from flask import Flask, request, jsonify,Response,stream_with_context,send_from_directory
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
from flask_cors import CORS
from werkzeug.utils import secure_filename
import os
from pathlib import Path
from openai import OpenAI
# from langchain_core.output_parsers import StrOutputParser
import json
from msgUtil import sendMessage
# import faiss
import uuid
# from langchain_community.embeddings import DashScopeEmbeddings
import dashscope
# from langchain.prompts.chat import ChatPromptTemplate
# from rank_bm25 import BM25Okapi
# Define the condense question prompt# Create a chain that uses the vector store
# from langchain_openai  import ChatOpenAI
# from langchain.prompts import PromptTemplate
from sqlalchemy.types import LargeBinary
# from PIL import Image
# import pytesseract
from bs4 import BeautifulSoup
# from pdfminer.high_level import extract_text as pdf_extract_text
import re
# from pdf2image import convert_from_path
# from docx import Document as DocxDocument
from datetime import timedelta
import re
import requests
from bs4 import BeautifulSoup
# from langchain_core.prompts import ChatPromptTemplate
# ocr = PaddleOCR(use_angle_cls=True, lang="ch")
import numpy as np
dashscope.api_key = "sk-3b641a53d21d47a1bd3095fed9c2360c"
import chardet
import os
import hashlib
import requests
import time
from alibabacloud_bailian20231229.client import Client as bailian20231229Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_bailian20231229 import models as bailian_20231229_models
from alibabacloud_tea_util import models as util_models
# from dashscope import Assistants, Messages, Runs, Threads
import dashscope
import oss2
# import sys
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from xingchen import (
    Configuration, CharacterPermissionConfig, ApiClient, ChatApiSub, ChatReqParams,
    CharacterUpdateDTO, CharacterKey, Message, UserProfile, ModelParameters, ChatContext,
    CharacterCreateDTO, CharacterApiSub,ChatHistoryQueryDTO,ChatHistoryQueryWhere,ChatMessageApiSub
)
import time
from typing import Optional
from pydantic import BaseModel
from datetime import datetime
from datetime import datetime
import pytz

def china_time():
    return datetime.now(pytz.timezone('Asia/Shanghai'))
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hanguang'
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024  # 50 MB
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://xkk:xkkxkkxkk@localhost:3306/rag_platform'
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://xkk:xkkxkkxkk@106.15.50.65:3306/rag_platform?charset=utf8mb4'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://xkk:xkkxkkxkk@47.93.223.157:12314/rag_platform'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
jwt = JWTManager(app)
from flask_jwt_extended import JWTManager, jwt_required, get_jwt_identity, verify_jwt_in_request

required_vars = {
    'ALIBABA_CLOUD_ACCESS_KEY_ID': 'LTAI5tACpJtxTNAJjfFkasoy',
    'ALIBABA_CLOUD_ACCESS_KEY_SECRET': 'umUpo3xYBJAQol7NJLUq3HGJvzHeYs',
    'DASHSCOPE_API_KEY': 'sk-b381f7cd26a146a99ab7ecd915f61d91',
    'WORKSPACE_ID': 'llm-9dbl0qcmd0i6f3bs'
}

dashscope.api_key = required_vars['DASHSCOPE_API_KEY']
# 阿里云OSS配置信息
OSS_ACCESS_KEY_ID = 'LTAI5tJHJfWUFdJ16ZxWxKxY'
OSS_ACCESS_KEY_SECRET = 'k2JMtRd5pRjvZ3Phv8NeUbQrj3enwz'
auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
# 设置Endpoint和Region
endpoint = "https://oss-cn-beijing.aliyuncs.com"
region = "cn-beijing"
bucket = oss2.Bucket(auth, endpoint, "zsai-algorithm", region=region)
def upload_to_oss(url, id, file_id, filename):
    with open(url, "rb") as f:
        oss_object_name = f"knowledgeDB/{id}/{file_id}.{filename.split('.')[-1]}"
        bucket.put_object(oss_object_name, f)
        print("OSS上传成功")
        return oss_object_name
jwt = JWTManager(app)

CORS(app)  # Enable CORS for specific origin

def create_client() -> bailian20231229Client:
    """
    创建并配置阿里云百炼客户端。

    返回:
        bailian20231229Client: 配置好的客户端。
    """
    config = open_api_models.Config(
        access_key_id=required_vars['ALIBABA_CLOUD_ACCESS_KEY_ID'],
        access_key_secret=required_vars['ALIBABA_CLOUD_ACCESS_KEY_SECRET']
    )
    config.endpoint = 'bailian.cn-beijing.aliyuncs.com'
    return bailian20231229Client(config)

def calculate_md5(file_path: str) -> str:
    """
    计算文件的 MD5 哈希值。

    参数:
        file_path (str): 文件路径。

    返回:
        str: 文件的 MD5 哈希值。
    """
    hash_md5 = hashlib.md5()
    with open(file_path, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

def get_file_size(file_path: str) -> int:
    """
    获取文件大小（以字节为单位）。

    参数:
        file_path (str): 文件路径。

    返回:
        int: 文件大小（以字节为单位）。
    """
    return os.path.getsize(file_path)

def apply_lease(client, category_id, file_name, file_md5, file_size, workspace_id):
    """
    从阿里云百炼服务申请文件上传租约。

    参数:
        client (bailian20231229Client): 阿里云百炼客户端。
        category_id (str): 类别 ID。
        file_name (str): 文件名称。
        file_md5 (str): 文件的 MD5 哈希值。
        file_size (int): 文件大小（以字节为单位）。
        workspace_id (str): 业务空间 ID。

    返回:
        阿里云百炼服务的响应。
    """
    headers = {}
    request = bailian_20231229_models.ApplyFileUploadLeaseRequest(
        file_name=file_name,
        md_5=file_md5,
        size_in_bytes=file_size,
    )
    runtime = util_models.RuntimeOptions()
    return client.apply_file_upload_lease_with_options(category_id, workspace_id, request, headers, runtime)


def add_category(client, category_name, category_type, workspace_id):
    headers = {}
    request = bailian_20231229_models.AddCategoryRequest(
        category_name = category_name,
        category_type = category_type
    )
    runtime = util_models.RuntimeOptions()
    return client.add_category_with_options(workspace_id, request, headers, runtime)

def bailian_upload_file(upload_url, headers, file_path):
    """
    将文件上传到阿里云百炼服务。

    参数:
        upload_url (str): 上传 URL。
        headers (dict): 上传请求的头部。
        file_path (str): 文件路径。
    """
    with open(file_path, 'rb') as f:
        file_content = f.read()
    upload_headers = {
        "X-bailian-extra": headers["X-bailian-extra"],
        "Content-Type": headers["Content-Type"]
    }
    response = requests.put(upload_url, data=file_content, headers=upload_headers)
    response.raise_for_status()

def add_file(client: bailian20231229Client, lease_id: str, parser: str, category_id: str, workspace_id: str):
    """
    将文件添加到阿里云百炼服务。

    参数:
        client (bailian20231229Client): 阿里云百炼客户端。
        lease_id (str): 租约 ID。
        parser (str): 用于文件的解析器。
        category_id (str): 类别 ID。
        workspace_id (str): 业务空间 ID。

    返回:
        阿里云百炼服务的响应。
    """
    headers = {}
    request = bailian_20231229_models.AddFileRequest(
        lease_id=lease_id,
        parser=parser,
        category_id=category_id,
    )
    runtime = util_models.RuntimeOptions()
    return client.add_file_with_options(workspace_id, request, headers, runtime)

def describe_file(client, workspace_id, file_id):
    """
    在阿里云百炼服务中描述文件。

    参数:
        client (bailian20231229Client): 阿里云百炼客户端。
        workspace_id (str): 业务空间 ID。
        file_id (str): 文件 ID。

    返回:
        阿里云百炼服务的响应。
    """
    headers = {}
    runtime = util_models.RuntimeOptions()
    return client.describe_file_with_options(workspace_id, file_id, headers, runtime)

def create_bailian_index(client, workspace_id, category_id, name, structure_type, source_type, sink_type):
    """
    为文件在阿里云百炼服务中创建知识库索引。

    参数:
        client (bailian20231229Client): 阿里云百炼客户端。
        workspace_id (str): 业务空间 ID。
        category_id (str): 文件 ID。
        name (str): 知识库索引名称。
        structure_type (str): 知识库的数据类型。
        source_type (str): 数据管理的数据类型。
        sink_type (str): 知识库的向量存储类型。

    返回:
        阿里云百炼服务的响应。
    """
    headers = {}
    request = bailian_20231229_models.CreateIndexRequest(
        structure_type=structure_type,
        name=name,
        source_type=source_type,
        sink_type=sink_type,
        category_ids=[category_id]
    )
    runtime = util_models.RuntimeOptions()
    return client.create_index_with_options(workspace_id, request, headers, runtime)

def submit_index(client, workspace_id, index_id):
    """
    向阿里云百炼服务提交索引任务。

    参数:
        client (bailian20231229Client): 阿里云百炼客户端。
        workspace_id (str): 业务空间 ID。
        index_id (str): 索引 ID。

    返回:
        阿里云百炼服务的响应。
    """
    headers = {}
    submit_index_job_request = bailian_20231229_models.SubmitIndexJobRequest(
        index_id=index_id
    )
    runtime = util_models.RuntimeOptions()
    return client.submit_index_job_with_options(workspace_id, submit_index_job_request, headers, runtime)

def retrieve_from_knowledge_base(client, workspace_id, index_id,query):
    """
    获取阿里云百炼服务中索引任务的状态。

    参数:
        client (bailian20231229Client): 阿里云百炼客户端。
        workspace_id (str): 业务空间 ID。
        job_id (str): 任务 ID。
        index_id (str): 索引 ID。

    返回:
        阿里云百炼服务的响应。
    """
    headers = {}
    req = bailian_20231229_models.RetrieveRequest(
        index_id=index_id,
        enable_rewrite=True,
        query=query
    )
    runtime = util_models.RuntimeOptions()
    return client.retrieve_with_options(workspace_id, req, headers, runtime)

def get_index_job_status(client, workspace_id, job_id, index_id):
    """
    获取阿里云百炼服务中索引任务的状态。

    参数:
        client (bailian20231229Client): 阿里云百炼客户端。
        workspace_id (str): 业务空间 ID。
        job_id (str): 任务 ID。
        index_id (str): 索引 ID。

    返回:
        阿里云百炼服务的响应。
    """
    headers = {}
    get_index_job_status_request = bailian_20231229_models.GetIndexJobStatusRequest(
        index_id=index_id,
        job_id=job_id
    )
    runtime = util_models.RuntimeOptions()
    return client.get_index_job_status_with_options(workspace_id, get_index_job_status_request, headers, runtime)

# 向百炼上传文件
def upload_bailian_file(category_id,index_id,file_path):
    
    # category_id = 'cate_032b7bf7c9d3452882282c26e1fa00ca_10492543'
    parser = 'DASHSCOPE_DOCMIND'
    # source_type = 'DATA_CENTER_CATEGORY'
    # structure_type = 'unstructured'
    # sink_type = 'DEFAULT'
    workspace_id = required_vars["WORKSPACE_ID"]
    print("步骤1：创建阿里云百炼客户端")
    client = create_client()

    # 步骤2：准备文件信息
    print("步骤2：准备文件信息")
    file_name = os.path.basename(file_path)
    file_md5 = calculate_md5(file_path)
    file_size = get_file_size(file_path)

    # 步骤3：申请上传租约
    print("步骤3：向阿里云百炼申请上传租约")
    lease_response = apply_lease(client, category_id, file_name, file_md5, file_size, workspace_id)
    print(lease_response)
    lease_id = lease_response.body.data.file_upload_lease_id
    upload_url = lease_response.body.data.param.url
    upload_headers = lease_response.body.data.param.headers

    # 步骤4：上传文件
    print("步骤4：上传文件到阿里云百炼")
    bailian_upload_file(upload_url, upload_headers, file_path)

    # 步骤5：将文件添加到服务器
    print("步骤5：将文件添加到阿里云百炼服务器")
    add_response = add_file(client, lease_id, parser, category_id, workspace_id)
    file_id = add_response.body.data.file_id

    # 步骤6：检查文件状态
    print("步骤6：检查阿里云百炼中的文件状态")
    while True:
        describe_response = describe_file(client, workspace_id, file_id)
        status = describe_response.body.data.status
        print(f"当前文件状态：{status}")
        if status == 'INIT':
            print("文件待解析，请稍候...")
        elif status == 'PARSING':
            print("文件解析中，请稍候...")
        elif status == 'PARSE_SUCCESS':
            print("文件解析完成！")
            break
        else:
            print(f"未知的文件状态：{status}，请联系技术支持。")
            return None
        time.sleep(5)

    print("步骤8：向阿里云百炼提交索引任务")
    submit_response = submit_index(client, workspace_id, index_id)
    print(submit_response)
    job_id = submit_response.body.data.id

    # 步骤9：获取索引任务状态
    print("步骤9：获取阿里云百炼索引任务状态")
    while True:
        get_index_job_status_response = get_index_job_status(client, workspace_id, job_id, index_id)
        status = get_index_job_status_response.body.data.status
        print(f"当前索引任务状态：{status}")
        if status == 'COMPLETED':
            break
        time.sleep(5)
    return file_id

def create_bailian_knowledge_base(
    category_name:str,
    workspace_id: str,
    name: str
):
    """
    使用阿里云百炼服务创建知识库。

    参数:
        file_path (str): 文件路径。
        workspace_id (str): 业务空间 ID。
        name (str): 知识库名称。

    返回:
        str or None: 如果成功，返回索引 ID；否则返回 None。
    """
    # 设置默认值
    category_id = 'cate_032b7bf7c9d3452882282c26e1fa00ca_10492543'
    parser = 'DASHSCOPE_DOCMIND'
    source_type = 'DATA_CENTER_CATEGORY'
    structure_type = 'unstructured'
    sink_type = 'DEFAULT'

    try:
        # 步骤1：创建客户端
        # print("步骤1：创建阿里云百炼客户端")
        client = create_client()

        category_response = add_category(client,category_name,"UNSTRUCTURED",workspace_id)
        category_id = category_response.body.data.category_id
        print("category_response::",category_response,category_id)
        # # 步骤2：准备文件信息
        # print("步骤2：准备文件信息")
        # file_name = os.path.basename(file_path)
        # file_md5 = calculate_md5(file_path)
        # file_size = get_file_size(file_path)

        # # 步骤3：申请上传租约
        # print("步骤3：向阿里云百炼申请上传租约")
        # lease_response = apply_lease(client, category_id, file_name, file_md5, file_size, workspace_id)
        # print(lease_response)
        # lease_id = lease_response.body.data.file_upload_lease_id
        # upload_url = lease_response.body.data.param.url
        # upload_headers = lease_response.body.data.param.headers

        # # 步骤4：上传文件
        # print("步骤4：上传文件到阿里云百炼")
        # bailian_upload_file(lease_id, upload_url, upload_headers, file_path)

        # # 步骤5：将文件添加到服务器
        # print("步骤5：将文件添加到阿里云百炼服务器")
        # add_response = add_file(client, lease_id, parser, category_id, workspace_id)
        # file_id = add_response.body.data.file_id

        # # 步骤6：检查文件状态
        # print("步骤6：检查阿里云百炼中的文件状态")
        # while True:
        #     describe_response = describe_file(client, workspace_id, file_id)
        #     status = describe_response.body.data.status
        #     print(f"当前文件状态：{status}")
        #     if status == 'INIT':
        #         print("文件待解析，请稍候...")
        #     elif status == 'PARSING':
        #         print("文件解析中，请稍候...")
        #     elif status == 'PARSE_SUCCESS':
        #         print("文件解析完成！")
        #         break
        #     else:
        #         print(f"未知的文件状态：{status}，请联系技术支持。")
        #         return None
        #     time.sleep(5)

        # 步骤7：创建知识文件索引
        print("步骤7：在阿里云百炼中创建知识文件索引")
        index_response = create_bailian_index(client, workspace_id, category_id, name, structure_type, source_type, sink_type)
        print(index_response)
        index_id = index_response.body.data.id
        print("index_id::",index_response,index_id)
        # 步骤8：提交索引任务
        # print("步骤8：向阿里云百炼提交索引任务")
        # submit_response = submit_index(client, workspace_id, index_id)
        # # print(submit_response)
        # # job_id = submit_response.body.data.id

        # # # 步骤9：获取索引任务状态
        # # print("步骤9：获取阿里云百炼索引任务状态")
        # # while True:
        # #     get_index_job_status_response = get_index_job_status(client, workspace_id, job_id, index_id)
        # #     status = get_index_job_status_response.body.data.status
        # #     print(f"当前索引任务状态：{status}")
        # #     if status == 'COMPLETED':
        # #         break
        # #     time.sleep(5)

        print("阿里云百炼知识库创建成功！",index_id)
        return category_id,index_id

    except Exception as e:
        print(f"发生错误：{e}")
        return None


@jwt.expired_token_loader
def my_expired_token_callback(jwt_header, jwt_payload):
    return jsonify({"code": 401,
                        "msg": "身份验证过期",
                        "data": {}
                        }), 401

@jwt.invalid_token_loader
def my_invalid_token_callback(error):
    return jsonify({"code": 401,
                        "msg": "身份验证无效",
                        "data": {}
                        }), 401

@jwt.unauthorized_loader
def my_unauthorized_loader(callback):
    return jsonify({"code": 401,
                        "msg": "身份未授权",
                        "data": {}
                        }), 401


UPLOAD_FOLDER = 'knowledgeDB'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'docx', 'png', 'jpg', 'jpeg', 'gif'}
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

def allowed_file(filename):
    return '.' in filename and \
        filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

class FeedBack(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), unique=False)
    tele = db.Column(db.String(15), unique=False)
    content = db.Column(db.String(1024), unique=False)
    reply = db.Column(db.String(1024), unique=False)
    score = db.Column(db.Integer, unique=False)
    create_time = db.Column(db.DateTime, default=china_time)
    update_time = db.Column(db.DateTime, onupdate=china_time)
    
    
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(60), unique=True)
    nickname = db.Column(db.String(60), unique=False)
    tele = db.Column(db.String(60), unique=False)
    birthday = db.Column(db.String(60), unique=False)
    
    openid = db.Column(db.String(255), unique=True)
    friends = db.Column(db.Text, nullable=False)
    email = db.Column(db.String(120), unique=False,)
    introduction = db.Column(db.String(120), unique=False)
    password = db.Column(db.String(60))
    
    # horoscope = db.Column(db.Text, nullable=False)
    
    knowledge_bases = db.relationship('KnowledgeBase', backref='user', lazy=True)
    devices = db.relationship('UserDevice', back_populates='user', lazy=True)
    reports = db.relationship('UserReport', back_populates='user', lazy=True)

class KnowledgeBase(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(1024), nullable=False)
    description = db.Column(db.Text, nullable=True)
    model = db.Column(db.String(50), nullable=False, default='含光')
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    allb_knowledge_base_id  = db.Column(db.String(1024), nullable=False)
    allb_category_id = db.Column(db.String(1024), nullable=False)
    documents = db.relationship('Document', backref='knowledge_base', lazy=True)

class Document(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(1024), nullable=False)
    path = db.Column(db.String(1024), nullable=False)
    knowledge_base_id = db.Column(db.Integer, db.ForeignKey('knowledge_base.id'), nullable=False)
    fragments = db.relationship('Fragment', backref='document', lazy=True)
    raw_filename = db.Column(db.String(1024), nullable=False)
    allb_file_id = db.Column(db.String(1024), nullable=False)

class Fragment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    text = db.Column(db.Text, nullable=False)
    embedding = db.Column(LargeBinary(length=(2 ** 32) - 1), nullable=False)
    document_id = db.Column(db.Integer, db.ForeignKey('document.id'), nullable=False)

@app.route("/user/register", methods=['POST'])
def register():
    data = request.get_json()
    hashed_password = bcrypt.generate_password_hash(data['password']).decode('utf-8')
    user = User(username=data['username'],email=data['username'],  password=hashed_password, nickname=data['nickname'])
    db.session.add(user)
    db.session.commit()
    return jsonify({"code": 200,
                        "msg": "注册成功",
                        "data": {'username': user.username,
                                'nickname': user.nickname}
                        }), 201

@app.route("/user/feedback", methods=['POST'])
def feedback():
    data = request.get_json()
    # hashed_password = bcrypt.generate_password_hash(data['password']).decode('utf-8')
    fb = FeedBack(name=data['name'],tele=data['tele'],  content=data["content"], score=data['score'])
    db.session.add(fb)
    db.session.commit()
    return jsonify({"code": 200,
                        "msg": "反馈成功",
                        "data": {}
                        }), 200
    
@app.route("/user/getFeedBackList", methods=['GET'])
def getFeedBackList():
    rs = {"code": 200, "msg": "获取成功", "data": {}}
    feedbacks = FeedBack.query.order_by(FeedBack.create_time.desc()).all()
    
    # print(devices)
    data = [
        {
            "id": feedback.id,
            "create_time": feedback.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            "name": feedback.name[0]+"小星",
            "content": feedback.content,
            "score":feedback.score,
            "reply":feedback.reply,
        }
        for feedback in feedbacks
    ]
    rs["data"] = data
    return jsonify(rs)

@app.route("/user/login", methods=['POST'])
def login():
    data = request.get_json()
    user = User.query.filter_by(email=data['username']).first()
    if user and bcrypt.check_password_hash(user.password, data['password']):
        # Convert user.id to string before creating the access token
        access_token = create_access_token(identity=str(user.id), expires_delta=timedelta(hours=102400))
        
        return jsonify({"code": 200,
                        "msg": "登陆成功",
                        "data": {'username': user.username,
                                'nickname': user.nickname,
                                'introduction': user.introduction,
                                'email': user.email,
                                'access_token':access_token}
                        }), 200
    else:
        return jsonify({"code": 500,
                        "msg": "用户名或密码错误",
                        "data": {}
                        }), 404
@app.route("/user/userInfo", methods=['GET'])
@jwt_required()
def get_user_info():
    current_user_id = get_jwt_identity()
    user = User.query.get(current_user_id)
    
    if not user:
        return jsonify({"code": 500,
                        "msg": "找不到该用户",
                        "data": {}
                        }), 404
    
    # Query devices associated with the user
    user_devices = UserDevice.query.filter_by(user_id=current_user_id).all()
    devices_list = []
    
    for user_device in user_devices:
        device = user_device.device
        voice = device.voice
        role_agent = device.role_agent
        chat_records = device.chat_records
        
        chat_records_list = []
        for record in chat_records:
            chat_records_list.append({
                "id": record.id,
                "role":"user",
                "content": record.user_question,
                "create_time": record.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                "update_time": record.create_time.strftime("%Y-%m-%d %H:%M:%S")
            })
            chat_records_list.append({
                "id": record.id,
                "role":"assistant",
                "content": record.assistant_reply,
                "create_time": record.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                "update_time": record.create_time.strftime("%Y-%m-%d %H:%M:%S")
            })
        
        devices_list.append({
                            "id": device.id,
                            "mac": device.mac,
                            "name": device.name,
                            "introduction": device.introduction,
                            "create_time": device.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                            "update_time": device.update_time.strftime("%Y-%m-%d %H:%M:%S"),
                            "location": device.location,
                            "memory": device.memory,
                            "role_agent_id": device.role_agent_id,
                            "voice": {
                                "id": voice.id,
                                "alias": voice.alias,
                                "name": voice.name
                            },
                            "role_agent": {
                                "id": role_agent.id,
                                "character_id": role_agent.character_id,
                                "name": role_agent.name,
                                "introduction": role_agent.introduction,
                                "basic_information": role_agent.basic_information,
                                "opening_line": role_agent.opening_line,
                                "traits": role_agent.traits,
                                "chat_example": role_agent.chat_example,
                                "type": role_agent.type,
                                "chat_objective": role_agent.chat_objective,
                                "sound": role_agent.sound,
                                "create_time": role_agent.create_time.strftime("%Y-%m-%d %H:%M:%S")
                            },
                            "history": chat_records_list
                        })
    
    return jsonify({"code": 200,
                    "msg": "查询用户信息成功",
                    "data": {
                        'username': user.username,
                        'nickname': user.nickname,
                        'introduction': user.introduction,
                        'email': user.email,
                        'device': devices_list
                    }
                    }), 200

@app.route("/knowledgeBases", methods=['GET'])
@jwt_required()
def get_knowledge_bases():
    current_user_id = get_jwt_identity()
    knowledge_bases = KnowledgeBase.query.filter_by(user_id=current_user_id).all()
    kb_list = [{'id': kb.id, 'name': kb.name, 'description': kb.description, 'model': kb.model} for kb in knowledge_bases]
    return jsonify({"code": 200,
                        "msg": "获取知识库列表成功",
                        "data": kb_list
                        }), 200

@app.route("/knowledgeBases/<int:id>", methods=['GET'])
@jwt_required()
def get_knowledge_base(id):
    current_user_id = get_jwt_identity()
    knowledge_base = KnowledgeBase.query.filter_by(id=id, user_id=current_user_id).first()
    if knowledge_base:
        return jsonify({"code": 200,
                        "msg": "获取知识库详情成功",
                        "data": {
                                    'id': knowledge_base.id,
                                    'name': knowledge_base.name,
                                    'description': knowledge_base.description,
                                    'model': knowledge_base.model
                                }
                        }), 200
    else:
        return jsonify({"code": 500,
                        "msg": "找不到知识库",
                        "data": {
                                    'id': knowledge_base.id,
                                    'name': knowledge_base.name,
                                    'description': knowledge_base.description,
                                    'model': knowledge_base.model
                                }
                        }), 404

@app.route("/knowledgeBases", methods=['POST'])
@jwt_required()
def create_knowledge_base():
    data = request.get_json()
    current_user_id = get_jwt_identity()
    allb_category_id,allb_knowledge_base_id = create_bailian_knowledge_base(data['name'],required_vars["WORKSPACE_ID"],data['name'])
    new_kb = KnowledgeBase(name=data['name'], description=data['description'], model=data['model'], user_id=current_user_id,allb_knowledge_base_id=allb_knowledge_base_id,allb_category_id=allb_category_id)
    db.session.add(new_kb)
    db.session.commit()
    
    # create_bailian_index()
    
    
    return jsonify({"code": 200,
                        "msg": "创建知识库成功",
                        "data": {
                                }
                        }), 201

@app.route("/knowledgeBases/<int:id>", methods=['PUT'])
@jwt_required()
def update_knowledge_base(id):
    data = request.get_json()
    current_user_id = get_jwt_identity()
    kb = KnowledgeBase.query.filter_by(id=id, user_id=current_user_id).first()
    if not kb:
        return jsonify({'message': 'Knowledge base not found'}), 404
    kb.name = data.get('name', kb.name)
    kb.description = data.get('description', kb.description)
    kb.model = data.get('model', kb.model)
    db.session.commit()
    return jsonify({"code": 200,
                        "msg": "更新知识库成功",
                        "data": {
                                }
                        }), 200

@app.route("/knowledgeBases/<int:id>", methods=['DELETE'])
@jwt_required()
def delete_knowledge_base(id):
    current_user_id = get_jwt_identity()
    kb = KnowledgeBase.query.filter_by(id=id, user_id=current_user_id).first()
    if not kb:
        return jsonify({'message': 'Knowledge base not found'}), 404
    db.session.delete(kb)
    db.session.commit()
    return jsonify({"code": 200,
                        "msg": "删除知识库成功",
                        "data": {
                                }
                        }), 200

# def save_faiss_index(knowledge_base_id, index, texts, ids):
#     index_path = os.path.join(app.config['UPLOAD_FOLDER'], str(knowledge_base_id), 'faiss_index.bin')
#     data_path = os.path.join(app.config['UPLOAD_FOLDER'], str(knowledge_base_id), 'faiss_data.pkl')
    
#     faiss.write_index(index, index_path)
#     with open(data_path, 'wb') as f:
#         pickle.dump((texts, ids), f)
    
#     print(f"FAISS index and data for knowledge base {knowledge_base_id} saved successfully.")
    
@app.route("/upload", methods=['POST'])
@jwt_required()
def upload_file():
    if 'file' not in request.files:
        return jsonify({"code": 500,
                        "msg": "缺少文件",
                        "data": {
                                }
                        }), 400
    
    knowledge_base_id = request.form.get('knowledge_base_id')
    
    if not knowledge_base_id:
        return  jsonify({"code": 500,
                        "msg": "缺少知识库ID",
                        "data": {
                                }
                        }), 400
    
    knowledge_base = KnowledgeBase.query.filter_by(id=knowledge_base_id).first()
    print("知识库信息为：",knowledge_base.allb_knowledge_base_id,knowledge_base.allb_category_id)
    allb_knowledge_base_id = knowledge_base.allb_knowledge_base_id
    allb_category_id = knowledge_base.allb_category_id
    files = request.files.getlist('file')
    uploaded_files = []
    
    for file in files:
        if file.filename == '':
            continue
        if file and allowed_file(file.filename):
            raw_filename = file.filename
            filename = "{}_{}_{}".format(file.filename.replace(' ',''),uuid.uuid4().hex,secure_filename(file.filename))
            absoulte_filepath = os.path.join(app.config['UPLOAD_FOLDER'],knowledge_base_id, filename)
            base_dir = os.path.join(os.getcwd(), 'knowledgeDB', str(knowledge_base_id))
            filepath = os.path.join(base_dir, file.filename)
            # if not os.path.exists(filepath):
            #     os.makedirs(filepath)
            # file.save(filepath)
            if not os.path.exists(base_dir):
                try:
                    os.makedirs(base_dir)
                except:
                    pass
            
            # Save the file
            file.save(filepath)
            upload_to_oss(filepath,knowledge_base_id,filename,filename)
            file_id = upload_bailian_file(allb_category_id,allb_knowledge_base_id,filepath)
            uploaded_files.append({'name': filename, 'path': filepath})
            # print(bailian_knowledge_base_id)
            # text_content = extract_text_from_file(filepath, filename)
            # if text_content:
            #     documents = split_text(raw_filename,text_content)
            #     # 暂且去掉embedding匹配
            #     # embeddings = generate_embeddings(documents)
            #     embeddings = [[]]
                
            # Save the document information to the database
            new_doc = Document(name=filename, path=absoulte_filepath, knowledge_base_id=int(knowledge_base_id),raw_filename=raw_filename,allb_file_id = file_id)
            db.session.add(new_doc)
            db.session.commit()
                
            #     # Save the fragments to the database
            #     new_fragments = []
            #     for i, (doc, emb) in enumerate(zip(documents, embeddings)):
            #         emb_bytes = pickle.dumps(emb)
            #         new_frag = Fragment(text=doc, embedding=emb_bytes, document_id=new_doc.id)
            #         new_fragments.append(new_frag)
            #     db.session.bulk_save_objects(new_fragments)
            #     db.session.commit()
                
            #     uploaded_files.append({'name': filename, 'path': filepath})
                
                
            #     # Add embeddings to the FAISS index for this knowledge base
            #     if knowledge_base_id not in faiss_indexes:
            #         d = len(embeddings[0])
            #         faiss_indexes[knowledge_base_id] = faiss.IndexFlatL2(d)
            #         fragment_texts_by_kb[knowledge_base_id] = []
            #         fragment_ids_by_kb[knowledge_base_id] = []
                
            #     embeddings_array = np.array(embeddings).astype('float32')
            #     faiss_indexes[knowledge_base_id].add(embeddings_array)
            #     fragment_texts_by_kb[knowledge_base_id].extend(documents)
            #     fragment_ids_by_kb[knowledge_base_id].extend([new_frag.id for new_frag in new_fragments])
                
            #     # Save the updated FAISS index and data to files
            #     save_faiss_index(knowledge_base_id, faiss_indexes[knowledge_base_id], fragment_texts_by_kb[knowledge_base_id], fragment_ids_by_kb[knowledge_base_id])
    
    if uploaded_files:
        return jsonify({"code": 200,
                        "msg": "上传成功",
                        "data": uploaded_files
                        }), 201
    else:
        return jsonify({"code": 500,
                        "msg": "上传无效文件",
                        "data": uploaded_files
                        }), 400
    
def extract_ocr_text(nested_list):
    text_content = []
    for sublist in nested_list:
        if isinstance(sublist, list) and len(sublist) == 2:
            _, text_tuple = sublist
            if isinstance(text_tuple, tuple) and len(text_tuple) == 2:
                text_content.append(text_tuple[0])
    return text_content

def extract_urls(raw_text):
    # 正则表达式匹配URL
    url_pattern = r'https?://[^\s]+'
    urls = re.findall(url_pattern, raw_text)
    return urls

def fetch_and_extract_text(urls):
    all_texts = []
    for url in urls:
        try:
            response = requests.get(url)
            response.raise_for_status()  # 如果响应状态码不是200，引发HTTPError异常
            detected_encoding = chardet.detect(response.content)['encoding']
            soup = BeautifulSoup(response.content, 'html.parser', from_encoding=detected_encoding)
            text = soup.get_text(separator='\n', strip=True)
            all_texts.append(text)
        except requests.RequestException as e:
            print(f"Failed to fetch {url}: {e}")
    return all_texts

# def extract_text_from_file(filepath, filename):
#     if filename.endswith('.txt'):
#         with open(filepath, 'r', encoding='utf-8') as f:
#             return f.read()
#     elif filename.endswith('.pdf'):
#         # Extract text using pdfminer
#         laparams = LAParams(char_margin=2.0, word_margin=0.1, line_margin=0.5, boxes_flow=None)
#         raw_text = pdf_extract_text(filepath, laparams=laparams)
        
#         # Extract images from PDF and perform OCR on them
#         images = convert_from_path(filepath)
#         for image in images:
#             try:
#                 result = ocr.ocr(np.array(image), cls=True)
#                 result = extract_ocr_text(result)
#                 raw_text += "\n" + '，'.join(result)
#             except:
#                 continue
#         # print("是否有图片:::",images,raw_text)
#         # 提取URL
#         # urls = extract_urls(raw_text)

#         # # 获取并提取每个URL中的文字内容
#         # texts_from_urls = fetch_and_extract_text(urls)

#         # # 打印提取的文字内容
#         # for i, text in enumerate(texts_from_urls, start=1):
#         #     print(f"\nText from URL {i}:\n{text}\n")
            
#         #     raw_text += "\n" + text
        
#         return raw_text.strip().replace("禁止下载","").replace("教学运营中心专用","")
#     elif filename.endswith('.docx'):
#         text = docx2txt.process(filepath)
        
#         # Extract links from the document (if any)
#         doc = DocxDocument(filepath)
#         link_texts = []
#         for rel in doc.part.rels.values():
#             if "hyperlink" in rel.reltype:
#                 link_texts.append(rel._target)
        
#         text += "\n" + " ".join(link_texts)
        
#         return text.strip()
#     else:
#         return None

# def split_text(raw_filename,text):
#     splitter = RecursiveCharacterTextSplitter(
#         chunk_size=800,
#         chunk_overlap=100
#     )
#     txts = splitter.split_text(text)
#     txts = ["{}_{}_{}".format(raw_filename,txts[0][:40],t.replace("\n",'').replace(' ','')) for t in txts]
#     # print(txts)
#     return txts

# def generate_embeddings(documents):
#     embeddings = DashScopeEmbeddings(
#         model="text-embedding-v2",
#         dashscope_api_key="sk-3b641a53d21d47a1bd3095fed9c2360c",
#     )
#     # vectorstore = FAISS.from_texts(documents, embeddings)
#     doc_results = embeddings.embed_documents(documents)
#     # print(doc_results)
#     # print(vectorstore.embeddings.shape)
#     return doc_results
# Initialize a dictionary to store FAISS indexes and data by knowledge_base_id
faiss_indexes = {}
fragment_texts_by_kb = {}
fragment_ids_by_kb = {}
# def load_faiss_index(knowledge_base_id):
#     index_path = os.path.join(app.config['UPLOAD_FOLDER'], str(knowledge_base_id), 'faiss_index.bin')
#     data_path = os.path.join(app.config['UPLOAD_FOLDER'], str(knowledge_base_id), 'faiss_data.pkl')
    
#     if not os.path.exists(index_path) or not os.path.exists(data_path):
#         print(f"FAISS index or data file for knowledge base {knowledge_base_id} does not exist.")
#         return False
    
#     # Load the FAISS index from the file
#     faiss_indexes[knowledge_base_id] = faiss.read_index(index_path)
    
#     # Load the corresponding texts and IDs from a separate file
#     with open(data_path, 'rb') as f:
#         fragment_texts_by_kb[knowledge_base_id], fragment_ids_by_kb[knowledge_base_id] = pickle.load(f)
    
#     print(f"FAISS index and data for knowledge base {knowledge_base_id} loaded successfully.")
#     return True
@app.route("/knowledgeBases/documents/<int:kb_id>", methods=['GET'])
@jwt_required()
def get_documents(kb_id):
    documents = Document.query.filter_by(knowledge_base_id=kb_id).all()
    docs_list = [{'id': doc.id, 'name': doc.name} for doc in documents]
    return  jsonify({"code": 200,
                        "msg": "查询成功",
                        "data": docs_list
                        }), 200

@app.route("/knowledgeBases/documents/<int:id>", methods=['DELETE'])
@jwt_required()
def delete_document(id):
    document = Document.query.get_or_404(id)
    
    # Delete all fragments associated with the document
    fragments = Fragment.query.filter_by(document_id=document.id).all()
    for fragment in fragments:
        db.session.delete(fragment)
    
    try:
        os.remove(document.path)
    except Exception as e:
        print(f"Error deleting file: {e}")
    
    db.session.delete(document)
    db.session.commit()
    
    return  jsonify({"code": 200,
                        "msg": "删除成功",
                        "data": {}
                        }), 200 

@app.route("/knowledgeBases/fragments/<int:kb_id>", methods=['GET'])
@jwt_required()
def get_fragments(kb_id):
    documents = Document.query.filter_by(knowledge_base_id=kb_id).all()
    fragment_ids = [doc.id for doc in documents]
    fragments = Fragment.query.options(db.defer(Fragment.embedding)).filter(Fragment.document_id.in_(fragment_ids)).limit(100).all()
    fragments_list = [{'id': frag.id, 'text': frag.text} for frag in fragments]
    return jsonify({"code": 200,
                        "msg": "查询知识片段成功",
                        "data": fragments_list
                        }), 200 

@app.route("/knowledgeBases/fragments/<int:id>", methods=['DELETE'])
@jwt_required()
def delete_fragment(id):
    fragment = Fragment.query.get_or_404(id)
    db.session.delete(fragment)
    db.session.commit()
    return jsonify({"code": 200,
                        "msg": "删除知识片段成功",
                        "data": {}
                        }), 200 

from pathlib import Path

@app.route("/aiSearch/questionRewrite", methods=['POST'])
def questionRewrite():
    print("searchDoc开始问题重写了")
    data = request.get_json()
    query = data['keywords'] or "'"
    history = data['history'] or "'"
    
    
    client = OpenAI(
        api_key="sk-3b641a53d21d47a1bd3095fed9c2360c",  # 替换成真实DashScope的API_KEY
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 填写DashScopebase_url
    )
    # 新文件上传后需要等待模型解析，首轮rt可能较长
    completion = client.chat.completions.create(
        model="qwen-long",
        messages=[
            {
                'role': 'system',
                'content': 'You are a helpful assistant.'
            },
            {
                'role': 'system',
                'content': '''
                            你是问题重写专家，我给了你一些我们历史的对话记录。
                            请你根据历史对话记录和我本次的提问，看看重写我的问题，使得我的问题更加清晰描述准确。你只输出重写后的问题即可，不要输出其他内容。
                            <历史对话记录>
                            {}
                            '''.format("{}".format(history))
                                                        
            },
            {
                'role': 'user',
                'content': f'我本次的问题是：{query}'
            }
        ],
        stream=False
    )
    print(completion.model_dump_json())
    rs = {"code":200,"msg":"重写成功","data":{}}
    print(completion.choices[0])
    rs["data"] = completion.choices[0].message.content
    return rs
    
    
    
from flask import Flask,jsonify
app.config['JSON_AS_ASCII'] = False
@app.route("/aiSearch/searchDoc", methods=['POST'])
def searchDoc():
    global cache_fragments
    print("searchDoc开始相似文档查询")
    data = request.get_json()
    knowledge_base_id_list = data['kdbListChecked'] if isinstance(data['kdbListChecked'], list) else [data['kdbListChecked']]
    query = data.get('keywords',"")
    qaList = data.get("qaList",[])
    print("qaList:::",qaList,type(qaList))
    
    if query is None or query=="":
        query = qaList[-1]["content"]
        print("r颗query为空::",query,qaList[-1]["content"])
        
    if qaList is None:
        qaList = []
        qaList.append({"role":"user","content":query})
    
    prompt = '''
    <你的角色>
    搜索分析师。
    <你的任务>
    根据我的聊天记录，直接输出我要搜什么内容，只返回搜索内容
    <我的聊天记录>
    {}
    '''.format(qaList)
    headers = {
        'Authorization': 'Bearer fastgpt-jUCLeQqMkGINXwqIJdkG5OHXUOSEdfJvQjM7aw8E8fXcd8SMdWDLlpygUCvQKnnW',
        'Content-Type': 'application/json'
    }
    data = {
        "stream": False,
        "detail": False,
        "messages": [
            {
                "content": "{}\n\n".format(prompt),
                "role": "user"
            }
        ]
    }
    resp = requests.post("https://fastgpt.zscampus.com/zs-fastgpt/api/v1/chat/completions",headers=headers,json=data)
    searchKeywords = resp.json()["choices"][0]["message"]["content"]
    print("优化后的搜索词：::",searchKeywords)
    # combined_results = []
    data.update({"kdbRS":[]})
    knowledge_base = KnowledgeBase.query.filter_by(id=knowledge_base_id_list[0]).first()
    workspace_id = required_vars["WORKSPACE_ID"]
    client = create_client()
    resp = retrieve_from_knowledge_base(client,workspace_id,knowledge_base.allb_knowledge_base_id,searchKeywords)
    print(resp)
    try:
        metadatas = [n.metadata for n in resp.body.data.nodes]
    except:
        pass
    print(len(resp.body.data.nodes))
    rs = {"code":200,"msg":"检索成功","data":{}}
    ragList = []
    for node in resp.body.data.nodes[:10]: 
        try:
            ragList.append(
                {
                    "content": node.metadata["content"],
                    "document_id": node.metadata["doc_id"],
                    "download_url": "zsai-algorithm/" + Path(Document.query.filter_by(allb_file_id=node.metadata["doc_id"]).first().path).as_posix()+'.pdf',
                    "file_name": node.metadata["title"],
                    "id": node.metadata["_id"], 
                    "kdb_id": knowledge_base_id_list[0]
                }
            )
        except Exception as e:
            print(e)
            continue
    rs["data"]={"kdbRSList": [{"kdbTitle": knowledge_base_id_list[0], "kdbRS": ragList}]}
    return jsonify(rs), 200
    # print(resp)
    # for knowledge_base_id in knowledge_base_id_list:
    #     if knowledge_base_id not in faiss_indexes:
    #         if not load_faiss_index(knowledge_base_id):
    #             continue
        
    #     # Fetch the fragments related to the knowledge base
    #     documents = Document.query.filter(Document.knowledge_base_id == knowledge_base_id).all()
    #     document_ids = [doc.id for doc in documents]
    #     print(f"searchDoc查到了文档, 共{len(documents)}个")
        
    #     if knowledge_base_id not in cache_fragments:
    #         fragments = Fragment.query.filter(Fragment.document_id.in_(document_ids)).options(db.defer(Fragment.embedding)).all()
    #         cache_fragments.update({knowledge_base_id:fragments})
    #     else:
    #         fragments = cache_fragments[knowledge_base_id]
        
    #     # Create a mapping from document_id to path
    #     document_path_map = {
    #         doc.id: {
    #             "path": doc.path,
    #             "raw_filename": doc.raw_filename,
    #             "knowledge_base_id": doc.knowledge_base_id
    #         } for doc in documents
    #     }
        
    #     # Extract texts
    #     original_texts = [frag.text for frag in fragments]
    #     # stripped_texts = [text.replace("\n", "").replace(" ", "") for text in original_texts]
        
    #     print("searchDoc处理完了知识的向量化")
        
    #     # Tokenize the original_texts using Jieba
    #     print("searchDoc开始BM25")
    #     tokenized_corpus = [preprocess_text(t) for t in original_texts]
    #     bm25 = BM25Okapi(tokenized_corpus)
        
    #     # Define a function to retrieve documents using BM25
    #     def bm25_retrieve(query, k=10):
    #         query_tokens = jieba.lcut(query.lower())
    #         scores = bm25.get_scores(query_tokens)
    #         top_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:k]
            
    #         # Ensure we do not exceed the length of texts or fragment_ids
    #         top_indices = [i for i in top_indices if i < len(original_texts)]
            
    #         # Debugging information
    #         print(f"Query Tokens: {query_tokens}")
    #         print(f"Scores: {scores}")
    #         print(f"Top Indices: {top_indices}")
            
    #         return [
    #             {
    #                 "content": original_texts[i],
    #                 "document_id": fragments[i].document_id,
    #                 "download_url": "zsai-algorithm/" + Path(document_path_map[fragments[i].document_id]["path"]).as_posix()+'.pdf',
    #                 "file_name": document_path_map[fragments[i].document_id]["raw_filename"],
    #                 "id": fragments[i].id,
    #                 "kdb_id": document_path_map[fragments[i].document_id]["knowledge_base_id"]
    #             } for i in top_indices
    #         ]
        
    #     # Define a function to retrieve documents using embeddings
    #     def embedding_retrieve(query, k=5):
    #         embedder = DashScopeEmbeddings(
    #             model="text-embedding-v2",
    #             dashscope_api_key="sk-3b641a53d21d47a1bd3095fed9c2360c",
    #         )
    #         question_embedding = np.array(embedder.embed_documents([query])).astype('float32').flatten()
    #         _, indices = faiss_indexes[knowledge_base_id].search(np.expand_dims(question_embedding, axis=0), k)
            
    #         retrieved_results = []
    #         for idx in indices.flatten():
    #             if idx < len(fragment_texts_by_kb[knowledge_base_id]):
    #                 text = fragment_texts_by_kb[knowledge_base_id][idx]
    #                 frag_id = fragment_ids_by_kb[knowledge_base_id][idx]
    #                 doc_id = next((f.document_id for f in fragments if f.id == frag_id), None)
    #                 if doc_id is not None:
    #                     retrieved_results.append({
    #                         "content": text,
    #                         "document_id": doc_id,
    #                         "download_url": "zsai-algorithm/" + Path(document_path_map[doc_id]["path"]).as_posix(),
    #                         "file_name": document_path_map[doc_id]["raw_filename"],
    #                         "id": frag_id,
    #                         "kdb_id": document_path_map[doc_id]["knowledge_base_id"]
    #                     })
            
    #         return retrieved_results
        
    #     # Retrieve documents using both BM25 and embeddings
    #     bm25_results = bm25_retrieve(question)
    #     embedding_results = embedding_retrieve(question)
        
    #     print("结束BM25和VEC")
        
    #     # Combine and deduplicate results
    #     combined_results.extend(bm25_results)
    #     combined_results.extend(embedding_results)
    #     combined_results = list({item['id']: item for item in combined_results}.values())  # Deduplicate by fragment ID
        
    #     # Rank combined results based on some criteria (simple example here)
    #     ranked_results = sorted(combined_results, key=lambda x: len(x["content"]), reverse=True)
        
    #     rs = {"code": 200, "data": {}, "msg": "检索成功"}
    #     rs["data"] = {"kdbRSList": [{"kdbTitle": knowledge_base_id, "kdbRS": ranked_results}]}
    #     return json.dumps(rs, ensure_ascii=False), 200

@app.route("/aiSearch/answer", methods=['POST'])
def answer():
    global cache_fragments
    print("answer开始AI对话")
    data = request.get_json()
    knowledge_base_id_list = data['kdbListChecked'] if isinstance(data['kdbListChecked'], list) else [data['kdbListChecked']]
    question = data.get('keywords',"")

    combined_results = []
    
    knowledge_base = KnowledgeBase.query.filter_by(id=knowledge_base_id_list[0]).first()
    workspace_id = required_vars["WORKSPACE_ID"]
    print("步骤1：创建阿里云百炼客户端")
    client = create_client()
    resp = retrieve_from_knowledge_base(client,workspace_id,knowledge_base.allb_knowledge_base_id,question)
    # metadatas = [n.metadata for n in resp.body.data.nodes]
    # print(len(resp.body.data.nodes))
    # rs = {"code":200,"msg":"检索成功","data":{}}
    data = [
        {
            "content": node.metadata["content"],
            "document_id": node.metadata["doc_id"],
            # "download_url": "zsai-algorithm/" + Path(document_path_map[fragments[i].document_id]["path"]).as_posix()+'.pdf',
            "file_name": node.metadata["title"],
            # "id": fragments[i].id,
            "kdb_id": knowledge_base_id_list[0]
        } for node in resp.body.data.nodes[:10]
    ]
    context = "\n".join(["{}.{}".format(index+1, result["content"].replace("\n", "").replace(" ", "")) for index, result in enumerate(data)])
    
    qa_prompt_template = """
    <你的角色>
    你是问题回答专家，能够回答一切问题，我给了你一些背景知识，并标注了序号。
    当你回答时，如果引用了某些知识，则在相关内容后标注引用的索引，格式为[x]，其中x为背景知识的索引序号。
    
    <示例>
     - **保研率**：大部分专业的保研率可达40%-50%，部分专业甚至高达50%-70%[7]。 - **名校青睐**：由于弘毅学堂在国内的知名度和认可度较高，很多名校在夏令营和预推免过程中对弘毅学堂的学生青睐有加[11]
    

    <背景知识>:\n
    """
    qa_prompt_template += context
    qa_prompt_template += "\n"
    qa_prompt_template += '''
    \n<我的问题>:\n
    '''
    qa_prompt_template += question

    qa_prompt_template += '''
    \n<你的回答>:\n
    '''
    
    client = OpenAI(
        api_key="sk-3b641a53d21d47a1bd3095fed9c2360c",  # 替换成真实DashScope的API_KEY
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 填写DashScopebase_url
    )
    # 新文件上传后需要等待模型解析，首轮rt可能较长
    completion = client.chat.completions.create(
        model="qwen-long",
        messages=[
            {
                'role': 'system',
                'content': 'You are a helpful assistant.'
            },
            {
                'role': 'system',
                'content': '''
                            你是问题回答专家，我给了你一些背景知识，并标注了序号。
                            当你回答时，如果引用了某些知识，则在相关内容后标注引用的索引，格式为[x]，其中x为背景知识的索引序号。
                            
                            <示例>
                            - **保研率**：大部分专业的保研率可达40%-50%，部分专业甚至高达50%-70%[7]。 - **名校青睐**：由于弘毅学堂在国内的知名度和认可度较高，很多名校在夏令营和预推免过程中对弘毅学堂的学生青睐有加[11]
                            '''
            },
            {
                'role': 'user',
                'content': f'{qa_prompt_template}'
            }
        ],
        stream=True
    )
    # chat_prompt = ChatPromptTemplate.from_template(qa_prompt_template)
    
    # # Function to generate streaming response
    def generate():
        for chunk in completion:
            if chunk.choices[0].delta.content is not None:
                c = chunk.choices[0].delta.content
                yield f"{c}"  # SSE format requires 'data:' prefix and double newline
            
    return Response(generate(), mimetype='text/event-stream')

@app.route("/aiSearch/v2/answer", methods=['POST'])
def answerV2():
    global cache_fragments
    print("answer开始AI对话")
    data = request.get_json()
    knowledge_base_id_list = data['kdbListChecked'] if isinstance(data['kdbListChecked'], list) else [data['kdbListChecked']]
    question = data.get('keywords',"")
    think =  data.get('think',False)
    
    combined_results = []
    
    knowledge_base = KnowledgeBase.query.filter_by(id=knowledge_base_id_list[0]).first()
    workspace_id = required_vars["WORKSPACE_ID"]
    print("步骤1：创建阿里云百炼客户端")
    client = create_client()
    resp = retrieve_from_knowledge_base(client,workspace_id,knowledge_base.allb_knowledge_base_id,question)
    # metadatas = [n.metadata for n in resp.body.data.nodes]
    # print(len(resp.body.data.nodes))
    # rs = {"code":200,"msg":"检索成功","data":{}}
    data = [
        {
            "content": node.metadata["content"],
            "document_id": node.metadata["doc_id"],
            # "download_url": "zsai-algorithm/" + Path(document_path_map[fragments[i].document_id]["path"]).as_posix()+'.pdf',
            "file_name": node.metadata["title"],
            # "id": fragments[i].id,
            "kdb_id": knowledge_base_id_list[0]
        } for node in resp.body.data.nodes[:10]
    ]
    # rs["data"]={"kdbRSList": [{"kdbTitle": knowledge_base_id_list[0], "kdbRS": data}]}
    
        # Prepare the final context for QA chain
    context = "\n".join(["{}.{}".format(index+1, result["content"].replace("\n", "").replace(" ", "")) for index, result in enumerate(data)])
    
    # Initialize LLM
    # llm = ChatOpenAI(
    #     api_key="sk-3b641a53d21d47a1bd3095fed9c2360c",
    #     base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    #     model="qwen-plus",
    # )
    qa_prompt_template = """
    <你的角色>
    你是问题回答专家，能够回答一切问题，我给了你一些背景知识，并标注了序号。
    当你回答时，如果引用了某些知识，则在相关内容后标注引用的索引，格式为[x]，其中x为背景知识的索引序号。
    
    <示例>
     - **保研率**：大部分专业的保研率可达40%-50%，部分专业甚至高达50%-70%[7]。 - **名校青睐**：由于弘毅学堂在国内的知名度和认可度较高，很多名校在夏令营和预推免过程中对弘毅学堂的学生青睐有加[11]
    

    <背景知识>:\n
    """
    qa_prompt_template += context
    qa_prompt_template += "\n"
    qa_prompt_template += '''
    \n<我的问题>:\n
    '''
    qa_prompt_template += question

    qa_prompt_template += '''
    \n<你的回答>:\n
    '''
    
    client = OpenAI(
        api_key="sk-3b641a53d21d47a1bd3095fed9c2360c",  # 替换成真实DashScope的API_KEY
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 填写DashScopebase_url
    )
    # 新文件上传后需要等待模型解析，首轮rt可能较长
    completion = client.chat.completions.create(
        model="qwen-long" if think==False else "deepseek-r1",
        messages=[
            {
                'role': 'system',
                'content': 'You are a helpful assistant.'
            },
            {
                'role': 'system',
                'content': '''
                            你是问题回答专家，我给了你一些背景知识，并标注了序号。
                            当你回答时，如果引用了某些知识，则在相关内容后标注引用的索引，格式为[x]，其中x为背景知识的索引序号。
                            
                            <示例>
                            - **保研率**：大部分专业的保研率可达40%-50%，部分专业甚至高达50%-70%[7]。 - **名校青睐**：由于弘毅学堂在国内的知名度和认可度较高，很多名校在夏令营和预推免过程中对弘毅学堂的学生青睐有加[11]
                            '''
            },
            {
                'role': 'user',
                'content': f'{qa_prompt_template}'
            }
        ],
        stream=True
    )
    # chat_prompt = ChatPromptTemplate.from_template(qa_prompt_template)
    
    # # Function to generate streaming response
    def generate():
        # input_dict = {"context": context, "question": question}
        # prompt = chat_prompt.format_messages(**input_dict)
        
        # # Directly stream the response from the LLM
        # for chunk in llm.stream(prompt):
        #     yield f"data: {chunk}\n\n"  # SSE format requires 'data:' prefix and double newline
        # if think == False:
        for chunk in completion:
            if chunk.choices[0].delta.content is not None or chunk.choices[0].delta.reasoning_content is not None:
                # c = chunk.choices[0].delta.content
                r = None
                c = None
                try:
                    r = chunk.choices[0].delta.reasoning_content or None
                except:
                    pass
                try:
                    c = chunk.choices[0].delta.content or None
                except:
                    pass
                c = {"reasoning_content":r,"content":c}
                yield f"data: {json.dumps(c,ensure_ascii=False)}\n\n"  # SSE format requires 'data:' prefix and double newline
        # else:
        #     for chunk in completion:
        #         if chunk.choices[0].delta.content is not None:
        #             c = chunk.choices[0].delta.content
        #             yield f"{c}"  # SSE format requires 'data:' prefix and double newline
            
    return Response(generate(), mimetype='text/event-stream')


import sseclient
@app.route("/aiSearch/v3/answer", methods=['POST'])
def answerV3():
    global cache_fragments
    print("answer开始AI对话")
    data = request.get_json()
    knowledge_base_id_list = data['kdbListChecked'] if isinstance(data['kdbListChecked'], list) else [data['kdbListChecked']]
    question = data.get('keywords',"")
    think =  data.get('think',False)
    appId =  data.get('appId',"")
    chatId =  data.get('chatId',"")
    qaList =  data.get('qaList',[])
    apiKey = data.get('apiKey',"")
    env = data.get("env","prod")
    stream = data.get("stream",True)
    variables = data.get("variables",{})
    detail = data.get("detail",False)
    think = data.get("think",False)
    appRoles = data.get("appRoles",{})
    combined_results = []
    print("拿到qaList：：：",qaList)
    if question is None:
        question=""
    if qaList is None:
        qaList = []
    if question=="":
        question = qaList[-1]["content"]
    if len(qaList) == 0:
        qaList.append({"role":"user","content":question})
        
    knowledge_base = KnowledgeBase.query.filter_by(id=knowledge_base_id_list[0]).first()
    workspace_id = required_vars["WORKSPACE_ID"]
    print("步骤1：创建阿里云百炼客户端",question)
    client = create_client()
    resp = retrieve_from_knowledge_base(client,workspace_id,knowledge_base.allb_knowledge_base_id,question)
    # metadatas = [n.metadata for n in resp.body.data.nodes]
    # print(len(resp.body.data.nodes))
    # rs = {"code":200,"msg":"检索成功","data":{}}
    data = [
        {
            "content": node.metadata["content"],
            "document_id": node.metadata["doc_id"],
            # "download_url": "zsai-algorithm/" + Path(document_path_map[fragments[i].document_id]["path"]).as_posix()+'.pdf',
            "file_name": node.metadata["title"],
            # "id": fragments[i].id,
            "kdb_id": knowledge_base_id_list[0]
        } for node in resp.body.data.nodes[:10]
    ]
    # rs["data"]={"kdbRSList": [{"kdbTitle": knowledge_base_id_list[0], "kdbRS": data}]}
    
        # Prepare the final context for QA chain
    context = "\n".join(["{}.{}".format(index+1, result["content"].replace("\n", "").replace(" ", "")) for index, result in enumerate(data)])
    
    # Initialize LLM
    # llm = ChatOpenAI(
    #     api_key="sk-3b641a53d21d47a1bd3095fed9c2360c",
    #     base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    #     model="qwen-plus",
    # )
    qa_prompt_template = """
    <你的角色>
    你是问题回答专家，能够回答一切问题，我给了你一些背景知识，并标注了序号。
    当你回答时，如果引用了某些知识，则在相关内容后标注引用的索引，格式为[x]，其中x为背景知识的索引序号。
    
    <示例>
     - **保研率**：大部分专业的保研率可达40%-50%，部分专业甚至高达50%-70%[7]。 - **名校青睐**：由于弘毅学堂在国内的知名度和认可度较高，很多名校在夏令营和预推免过程中对弘毅学堂的学生青睐有加[11]
    

    <背景知识>:\n
    """
    qa_prompt_template += context
    qa_prompt_template += "\n"
    qa_prompt_template += '''
    \n<我的问题>:\n
    '''
    qa_prompt_template += question

    qa_prompt_template += '''
    \n<你的回答>:\n
    '''
    
    
    
    
    
    timeStart = time.time()
    
    if appId=="" or apiKey=="":
    
        client = OpenAI(
            api_key="sk-3b641a53d21d47a1bd3095fed9c2360c",  # 替换成真实DashScope的API_KEY
            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 填写DashScopebase_url
        )
        # 新文件上传后需要等待模型解析，首轮rt可能较长
        completion = client.chat.completions.create(
            model="qwen-long",
            messages=[
                {
                    'role': 'system',
                    'content': 'You are a helpful assistant.'
                },
                {
                    'role': 'system',
                    'content': '''
                                你是问题回答专家，我给了你一些背景知识，并标注了序号。
                                当你回答时，如果引用了某些知识，则在相关内容后标注引用的索引，格式为[x]，其中x为背景知识的索引序号。
                                
                                <示例>
                                - **保研率**：大部分专业的保研率可达40%-50%，部分专业甚至高达50%-70%[7]。 - **名校青睐**：由于弘毅学堂在国内的知名度和认可度较高，很多名校在夏令营和预推免过程中对弘毅学堂的学生青睐有加[11]
                                '''
                },
                {
                    'role': 'user',
                    'content': f'{qa_prompt_template}'
                }
            ],
            stream=True
        )
        # chat_prompt = ChatPromptTemplate.from_template(qa_prompt_template)
        
        # # Function to generate streaming response
        def generate():
            for chunk in completion:
                if chunk.choices[0].delta.content is not None:
                    c = chunk.choices[0].delta.content
                    
                    s = json.dumps({"code":200,"msg":"success","data":{"content":c},'timeElapsed':'{}'.format(time.time()-timeStart)},ensure_ascii=False)
                    # s = "event: " + eventType + "\n"
                        # print(s)
                        # s += "data: " + eventData + "\n\n"
                    # rs[]=
                    yield "data: {}\n\n".format(s)
                
        return Response(generate(), mimetype='text/event-stream')

    
    header={
        'Authorization': 'Bearer {}'.format(apiKey),
        'Content-Type': 'application/json'
    }
    if qaList[-1]["role"]=="user":
        qaList[-1]["content"] += qa_prompt_template
    else:
        qaList.append({"role":"user","content":qa_prompt_template})
        
    data={
        "chatId": chatId,
        "stream": stream,
        "detail": detail,
        "messages":qaList,
        "variables":variables,
        "appRoles":appRoles,
        "appId":appId,
        "think":think,
        "qaList":qaList
    }
    
    # print('header:::',header)
    # print("Stream:::,",stream)
    resp = requests.post('https://fastgpt.zscampus.com/zs-fastgpt/api/v1/chat/completions' if env=='prod' or env=='test' else "http://39.106.45.137:3000/api/v1/chat/completions",data=json.dumps(data),headers=header,stream=stream)
    print("当前Stream:::",stream)
    if not stream:
        item = resp.json()['choices'][0]["message"]
        print(item)
        reasoning_content = ""
        if detail==False:
            if "reasoning_content" in item:
                reasoning_content = item["reasoning_content"]
            if "content" in item:
                content = item["content"]
        else:
            try:
                for detail_item in  item["content"]:
                    print("detail_item_content:::",item["content"])
                    if "type" in detail_item and detail_item["type"]=="reasoning":
                        reasoning_content = detail_item["reasoning"]["content"]
                    elif "type" in detail_item and detail_item["type"]=="text":
                        content = detail_item["text"]["content"]
                    else:
                        content = item["content"]
            except:
                if "reasoning_content" in item:
                    reasoning_content = item["reasoning_content"]
                if "content" in item:
                    content = item["content"]
                
            
        eventData = {"code":200,"msg":"success","data":{"reasoning_content":reasoning_content,"content":content,"responseData":resp.json()["responseData"] if detail==True else {}},'timeElapsed':'{}'.format(time.time()-timeStart)}
        # s = "event: " + eventType + "\n"
        # s = eventData
        print("非流式返回：",eventData)
        return jsonify(eventData)
        
    
    if stream:
        headers = {
            'Content-Type': 'text/event-stream' ,
            'Cache-Control': 'no-cache',
            'Content-Encoding':None,
            'X-Accel-Buffering': 'no'
        }
    else:
        headers = {
            'Content-Type':'application/json',
            'Cache-Control': 'no-cache'
        }
    # for chunk in resp.iter_content(chunk_size=1024):
    # 处理响应内容
        # request = requests.post(reqUrl, stream=True, headers=reqHeaders, json=reqBody)
    def genReply():
        client = sseclient.SSEClient(resp)
        reasoning_content=""
        for event in client.events():
            s=""
            try:
                if 'choices' in json.loads(event.data).keys():
                    # print(json.loads(event.data))
                    if json.loads(event.data)['choices'][0]['finish_reason'] is None:
                    # print(event.data)
                        s = json.loads(event.data)['choices'][0]['delta']['content']
            except:
                s=""
            # eventType = "message"
            
            # rs['data']={'content':answer,}
            isOver=False if event.data!= '[DONE]' else True
            # if '[DONE]'
            eventData = json.dumps({"code":200,"msg":"success","data":{"reasoning_content":None,"content":s,},'timeElapsed':'{}'.format(time.time()-timeStart)},ensure_ascii=False)
            # s = "event: " + eventType + "\n"
            s = eventData
                # print(s)
                # s += "data: " + eventData + "\n\n"
            # rs[]=
            yield "data: {}\n\n".format(s)
            if event.event == "flowResponses" and detail:
                eventData = json.dumps({"code":200,"msg":"success","data":{"reasoning_content":reasoning_content,"content":s,"responseData":json.loads(event.data)},'timeElapsed':'{}'.format(time.time()-timeStart)},ensure_ascii=False)
                # s = "event: " + eventType + "\n"
                    # print(s)
                    # s += "data: " + eventData + "\n\n"
                # rs[]=
                yield "data: {}\n\n".format(s)
    return Response(genReply(), mimetype='text/event-stream', headers=headers)


#角色智能体相关

class RoleAgent(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    character_id = db.Column(db.String(1024), nullable=False)
    name = db.Column(db.String(1024), nullable=False, default="美髯公关云长")
    introduction = db.Column(db.Text, nullable=False, default="满腔忠义的关云长")
    basic_information = db.Column(db.Text, nullable=False, default="我们来玩一个角色扮演的游戏， 你是「满腔忠义的关云长」。")
    opening_line = db.Column(db.Text, nullable=False, default="我是「关云长」，很高心与你玩游戏")
    traits = db.Column(db.Text, nullable=False, default="请在对话时尽可能的展现你的性格、感情， 用文言文回答， 并使用古人的语气和用词。")
    chat_example = db.Column(db.Text, nullable=True)
    type = db.Column(db.String(50), nullable=False, default="virtual")
    chat_objective = db.Column(db.Text, nullable=False, default="我要关羽拜把子")
    sound = db.Column(db.String(1024), nullable=True)
    create_time = db.Column(db.DateTime, default=china_time)
    devices = db.relationship('Device', back_populates='role_agent')
    
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

def init_character_api_sub_client():
    configuration = Configuration(
        host="https://nlp.aliyuncs.com"
    )
    xingchen_ak = "lm-qbcQwj0A9CKMEBu4YkVe6Q=="
    configuration.access_token = xingchen_ak
    api_client = ApiClient(configuration)
    return CharacterApiSub(api_client)

def init_chat_message_client():
    configuration = Configuration(
        host="https://nlp.aliyuncs.com"
    )
    xingchen_ak = "lm-qbcQwj0A9CKMEBu4YkVe6Q=="
    configuration.access_token = xingchen_ak
    with ApiClient(configuration) as api_client:
        api_instance = ChatMessageApiSub(api_client)
    return api_instance

def init_chat_client():
    configuration = Configuration(
        host="https://nlp.aliyuncs.com"
    )
    xingchen_ak = "lm-qbcQwj0A9CKMEBu4YkVe6Q=="
    configuration.access_token = xingchen_ak
    api_client = ApiClient(configuration)
    return ChatApiSub(api_client)

xingchen_character_api = init_character_api_sub_client()
xingchen_chat_api = init_chat_client()

class UserMessage(BaseModel):
    character_id: Optional[str] = ""
    nick_name: Optional[str] = ""
    user_id: Optional[str] = ""
    content: Optional[str] = ""

def build_chat_param(msg: UserMessage):
    return ChatReqParams(
        bot_profile=CharacterKey(
            character_id=msg.character_id
        ),
        model_parameters=ModelParameters(
            seed=int(time.time()),
            incremental_output=True
        ),
        messages=[
            # Message(
            #     name='小星',
            #     role='assistant',
            #     content='我如果发现了你不想和我聊天或者我不想和你聊天了，我就会在 结尾说一句，“拜拜啦！”来结束本次聊天。'
            # ),
            Message(
                name=msg.nick_name,
                role='user',
                content=msg.content
            )
        ],
        context=ChatContext(
            use_chat_history=True
        ),
        user_profile=UserProfile(
            user_id=msg.user_id or uuid.uuid4().hex,
            user_name=msg.nick_name or "主人"
        )
    )

@app.route("/roleAgent/chatAsync", methods=['POST'])
def chat_async():
    data = request.json
    # current_user_id = get_jwt_identity()
    msg = UserMessage(**data)
    # msg.user_id = current_user_id or ""  # 确保 user_id 不会是 None
    
    if not msg.user_id:
        return {"error": "请填写用户ID"}, 400
    chat_param = build_chat_param(msg)
    chat_param.streaming = True  # 假设 streaming 是支持的

    def generate():
        responses = xingchen_chat_api.chat(chat_param)
        # print(responses)
        try:
            # print(responses)
            for res in responses:
                # print(res)
                reply = res.data.choices[0].messages[0].content
                yield reply
            yield "|"
        except:
            yield "你说的内容有些敏感呢，我还无法回答，嘻嘻，抱歉啦。|"

    # return Response(stream_with_context(generate()), content_type='text/event-stream')
    return Response(generate(), content_type='text/event-stream')


@app.route("/roleAgent/createRoleAgent", methods=['POST'])
@jwt_required()
def create_role_agent():
    data = request.json
    current_user_id = get_jwt_identity()
    params = RoleAgent(**data)
    rs = {"code": 200, "msg": "创建成功", "data": {}}
    name = params.name
    body = CharacterCreateDTO(
        name=name,
        introduction=params.introduction,
        basic_information="我们来玩一个角色扮演的游戏， 你是[{}]。".format(name)+params.basic_information+'''。\n<注意事项>\n当检测到以下情况时，需在回复末尾添加「拜拜啦～」来结束对话：
1️⃣ 用户显性退出信号：用户发送明确结束对话的语句（例如「别烦我了」「我要忙了」「你退下吧」，但不局限于这些）；
2️⃣ 情感攻击/负面情绪：识别用户意图包含「讨厌」「烦死了」「滚开」等词汇，或语气词带有明显不满；
3️⃣ 话题失控/违规内容：对话涉及敏感词或无法提供合理回答；
4️⃣ 沉默超时：用户消失了一段时间，回复并追加“拜拜啦”退场。,''',
        opening_line=params.opening_line or '你好',
        traits="请在对话时尽可能的展现你的性格、感情。"+','.join(params.traits)+'''。\n<注意事项>\n当检测到以下情况时，需在回复末尾添加「拜拜啦～」来结束对话：
1️⃣ 用户显性退出信号：用户发送明确结束对话的语句（例如「别烦我了」「我要忙了」「你退下吧」，但不局限于这些）；
2️⃣ 情感攻击/负面情绪：识别用户意图包含「讨厌」「烦死了」「滚开」等词汇，或语气词带有明显不满；
3️⃣ 话题失控/违规内容：对话涉及敏感词或无法提供合理回答；
4️⃣ 沉默超时：用户消失了一段时间，回复并追加“拜拜啦”退场。,''',
        chatExample="{{user}}:不想聊啦，没意思，滚蛋，别烦我了，你花好多。\n{{char}}:哼，那就拜拜啦。",
        chat_objective=params.chat_objective,
        perm_config=CharacterPermissionConfig(
            allow_api=0,
            allow_chat=0,
            is_public=0,
        ),
    )
    result = xingchen_character_api.create(character_create_dto=body)
    new_role_agent = RoleAgent(
        name=name,
        character_id=result.data.character_id,
        opening_line=params.opening_line,
        traits=','.join(params.traits),
        chat_objective=params.chat_objective,
        basic_information=params.basic_information,
        introduction=params.introduction,
        sound=params.sound,
        user_id=current_user_id
    )
    db.session.add(new_role_agent)
    db.session.commit()
    print(result)
    rs["data"] = {"character_id":result.data.character_id}
    return jsonify(rs), 200

@app.route("/roleAgent/updateRoleAgent", methods=['POST'])
def update_role_agent():
    data = request.json
    params = RoleAgent(**data)
    rs = {"code": 200, "msg": "修改成功", "data": {}}
    body = CharacterUpdateDTO(
        character_id=params.character_id,
        name=params.name,
        introduction=params.introduction,        
        basic_information="我们来玩一个角色扮演的游戏， 你是[{}]。".format(params.name)+params.basic_information+'''。\n<注意事项>\n当检测到以下情况时，需在回复末尾添加「拜拜啦～」来结束对话：
1️⃣ 用户显性退出信号：用户发送明确结束对话的语句（例如「别烦我了」「我要忙了」「你退下吧」，但不局限于这些）；
2️⃣ 情感攻击/负面情绪：识别用户意图包含「讨厌」「烦死了」「滚开」等词汇，或语气词带有明显不满；
3️⃣ 话题失控/违规内容：对话涉及敏感词或无法提供合理回答；
4️⃣ 沉默超时：用户消失了一段时间，回复并追加“拜拜啦”退场。,''',
        opening_line=params.opening_line,
        # traits=params.traits,
        traits="请在对话时尽可能的展现你的性格、感情。"+','.join(params.traits)+'''。\n<注意事项>\n当检测到以下情况时，需在回复末尾添加「拜拜啦～」来结束对话：
1️⃣ 用户显性退出信号：用户发送明确结束对话的语句（例如「别烦我了」「我要忙了」「你退下吧」，但不局限于这些）；
2️⃣ 情感攻击/负面情绪：识别用户意图包含「讨厌」「烦死了」「滚开」等词汇，或语气词带有明显不满；
3️⃣ 话题失控/违规内容：对话涉及敏感词或无法提供合理回答；
4️⃣ 沉默超时：用户消失了一段时间，回复并追加“拜拜啦”退场。,''',
        chatExample="{{user}}:不想聊啦，没意思，滚蛋，别烦我了，你话好多。\n{{char}}:哼，那就拜拜啦。",
        chat_objective=params.chat_objective,
        perm_config=CharacterPermissionConfig(
            allow_api=0,
            allow_chat=0,
            is_public=0,
        ),
        long_term_memories=[
            LongTermMemory(
                enabled=True,
                memory_type="kv",
                kv_memory_configs=[
                    KVMemoryConfig(
                        enabled=True,
                        memory_text="爱好"
                    ),
                    KVMemoryConfig(
                        enabled=True,
                        memory_text="特长"
                    ),
                    KVMemoryConfig(
                        enabled=True,
                        memory_text="工作"
                    ),
                    KVMemoryConfig(
                        enabled=True,
                        memory_text="基本信息"
                    ),
                    KVMemoryConfig(
                        enabled=True,
                        memory_text="关键事件"
                    ),
                    KVMemoryConfig(
                        enabled=True,
                        memory_text="社交关系"
                    )
                ]
            )
        ],
    )
    result = xingchen_character_api.update(character_update_dto=body)
    role_agent = RoleAgent.query.get(params.id)
    if role_agent:
        role_agent.name = params.name
        role_agent.opening_line = params.opening_line
        role_agent.traits = ','.join(params.traits)
        role_agent.chat_objective = params.chat_objective
        role_agent.basic_information = params.basic_information
        role_agent.introduction = params.introduction
        role_agent.sound = params.sound
        db.session.commit()
    else:
        rs["code"] = 404
        rs["msg"] = "角色代理未找到"
    rs["data"] = result.data
    return jsonify(rs), 200

@app.route("/roleAgent/getRoleAgentChatHistoryById", methods=['POST'])
@jwt_required()
def getRoleAgentChatHistoryById():
    current_user_id = get_jwt_identity()
    print("获取的用户ID聊天记录：：",current_user_id)
    rs = {"code": 200, "msg": "查询成功", "data": {}}
    id = request.json.get("id")
    roleAgent = RoleAgent.query.filter_by(id=id).first()
    # for i in 10:
    body = ChatHistoryQueryDTO(
        where=ChatHistoryQueryWhere(
            characterId=roleAgent.character_id,
            bizUserId=current_user_id,
            # pageSize=10000,
            # sessionId="7ed48d9881b54ed49d6967be7be01743"
            # startTime="1970-01-01T00:00:00.00Z",
            # endTime="1970-01-01T00:00:00.00Z",
            # messageIds=[
            #     "e5bfc3c7809e47c5ac17181250adcf2b"
            # ],

        ),
        orderBy=[
            "gmtCreate desc"
        ],
        pageNum=1,
        pageSize=500
    )

    # 对话历史
    history = init_chat_message_client().chat_histories(chat_history_query_dto=body).data
    records = []
    # for record in records:
    for h in history.list:
        # print(h.message_type)
        content = json.loads(h.content)["choices"][0]["messages"][0]["content"]
        if h.message_type=="user":
            records.append({"role": "user", "content": content, "create_time": h.gmt_create.strftime("%Y-%m-%d %H:%M:%S")})
        else:
            records.append({"role": "assistant", "content": content, "create_time": h.gmt_create.strftime("%Y-%m-%d %H:%M:%S")})
    # print(result)
    # role_agents = RoleAgent.query.all()
    # data = [
    #     {
    #         "id": ra.id,
    #         "name": ra.name,
    #         "character_id": ra.character_id,
    #         "opening_line": ra.opening_line,
    #         "traits": ra.traits.split(','),
    #         "chat_objective": ra.chat_objective,
    #         "basic_information": ra.basic_information,
    #         "introduction": ra.introduction,
    #         "sound": ra.sound,
    #         "create_time": ra.create_time.strftime("%Y-%m-%d %H:%M:%S")
    #     }
    #     for ra in role_agents
    # ]
    # records.sort(lambda x:x["create_time"],)
    # records = sorted(records,key=lambda x:x["create_time"],reverse=True)
    rs["data"] = records[::-1]
    return jsonify(rs), 200


@app.route("/roleAgent/getRoleAgentList", methods=['POST'])
def get_role_agent_list():
    rs = {"code": 200, "msg": "查询成功", "data": {}}
    role_agents = RoleAgent.query.all()
    data = [
        {
            "id": ra.id,
            "name": ra.name,
            "character_id": ra.character_id,
            "opening_line": ra.opening_line,
            "traits": ra.traits.split(','),
            "chat_objective": ra.chat_objective,
            "basic_information": ra.basic_information,
            "introduction": ra.introduction,
            "sound": ra.sound,
            "create_time": ra.create_time.strftime("%Y-%m-%d %H:%M:%S")
        }
        for ra in role_agents
    ]
    rs["data"] = data
    return jsonify(rs), 200

@app.route("/roleAgent/deleteRoleAgent", methods=['POST'])
def delete_role_agent():
    data = request.json
    params = RoleAgent(**data)
    rs = {"code": 200, "msg": "删除成功", "data": {}}
    role_agent = RoleAgent.query.get(params.id)
    if role_agent:
        xingchen_character_api.delete(character_id=role_agent.character_id)
        db.session.delete(role_agent)
        db.session.commit()
    else:
        rs["code"] = 404
        rs["msg"] = "角色代理未找到"
    return jsonify(rs), 200

@app.route("/roleAgent/getRoleAgentInfo", methods=['POST'])
def get_role_agent_info():
    data = request.json
    params = RoleAgent(**data)
    rs = {"code": 200, "msg": "查询成功", "data": {}}
    role_agent = RoleAgent.query.get(params.id)
    if role_agent:
        data = {
            "id": role_agent.id,
            "name": role_agent.name,
            "character_id": role_agent.character_id,
            "opening_line": role_agent.opening_line,
            "traits": role_agent.traits.split(','),
            "chat_objective": role_agent.chat_objective,
            "basic_information": role_agent.basic_information,
            "introduction": role_agent.introduction,
            "sound": role_agent.sound,
            "user_id": role_agent.user_id,
            "create_time": role_agent.create_time.strftime("%Y-%m-%d %H:%M:%S")
        }
        rs["data"] = data
    else:
        rs["code"] = 404
        rs["msg"] = "角色代理未找到"
    return jsonify(rs), 200


## 智能设备代码

class Device(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    mac = db.Column(db.String(255), nullable=False, unique=True)
    name = db.Column(db.String(1024), nullable=False, default="尚未被主人起名的小新")
    introduction = db.Column(db.Text, nullable=False, default="主人太懒了，还没介绍这位小新同学")
    create_time = db.Column(db.DateTime, default=china_time)
    update_time = db.Column(db.DateTime, onupdate=china_time,default=china_time)
    location = db.Column(db.String(1024), nullable=False, default="北京")
    memory = db.Column(db.Text, nullable=True)
    role_agent_id = db.Column(db.Integer,db.ForeignKey('role_agent.id'), nullable=False,default=5)
    history = db.Column(db.JSON, nullable=True, default=[])
    voice_id = db.Column(db.Integer,db.ForeignKey('voice.id'), nullable=False,default=38)
    
    
    user_devices = db.relationship('UserDevice', back_populates='device')
    role_agent = db.relationship('RoleAgent', back_populates='devices')
    chat_records = db.relationship('ChatRecord', back_populates='device')
    voice = db.relationship('Voice', back_populates='devices')
    
class UserDevice(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    device_id = db.Column(db.Integer, db.ForeignKey('device.id'), nullable=False)
    create_time = db.Column(db.DateTime, default=china_time)

    user = db.relationship('User', back_populates='devices')
    device = db.relationship('Device', back_populates='user_devices')

class UserReport(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    # device_id = db.Column(db.Integer, db.ForeignKey('device.id'), nullable=False)
    create_time = db.Column(db.DateTime, default=china_time)
    content = db.Column(db.Text, nullable=True)
    think = db.Column(db.Text, nullable=True)
    user = db.relationship('User', back_populates='reports')
    type = db.Column(db.String(255), nullable=False,default="chat")
    # device = db.relationship('Device', back_populates='user_devices')

class ChatRecord(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    device_id = db.Column(db.Integer, db.ForeignKey('device.id'), nullable=False)
    device_mac = db.Column(db.String(255), nullable=False)
    user_question = db.Column(db.Text, nullable=False)
    assistant_reply = db.Column(db.Text, nullable=False)
    create_time = db.Column(db.DateTime, default=china_time)
    update_time = db.Column(db.DateTime, onupdate=china_time)
    device = db.relationship('Device', back_populates='chat_records')

class DeviceInput(BaseModel):
    id: Optional[int] = 0
    mac: Optional[str] = ""
    #voice: Optional[str] = ""
    name: Optional[str] = "尚未被主人起名的小新"
    introduction: Optional[str] = "主人太懒了，还没介绍这位小新同学"
    create_time: Optional[datetime] = None
    update_time: Optional[datetime] = None
    location: Optional[str] = "北京"
    memory: Optional[str] = ""
    role_agent_id: Optional[int] = 3
    voice_id: Optional[int] = 3
    history: Optional[list] = []

class ChatRecordInput(BaseModel):
    id: Optional[int] = 0
    device_id: Optional[str] = ""
    device_mac: Optional[str] = ""
    user_question: Optional[str] = ""
    assistant_reply: Optional[str] = ""
    create_time: Optional[datetime] = None
    update_time: Optional[datetime] = None

# 解析 IP 地址所在地的函数
def get_ip_location(ip_address: str) -> dict:
    # print(f"https://ipinfo.io/{ip_address}/json")
    resp={"city":'中国'}
    try:
        response = requests.get(f"https://ipinfo.io/{ip_address}/json",timeout=0.2)
        resp = response.json()
        print(resp)
    except:
        resp={}
    return resp

@app.route("/deviceBank/loginDevice", methods=['POST'])
def loginDevice():
    rs = {"code": 200, "msg": "登录成功", "data": {}}
    params = DeviceInput(**request.json)
    # 从请求中获取客户端 IP 地址
    client_ip = request.remote_addr
    # 如果使用反向代理，可能需要从 X-Forwarded-For 头中获取 IP 地址
    x_forwarded_for = request.headers.get('x-forwarded-for')
    if x_forwarded_for:
        # X-Forwarded-For 头可能包含多个 IP 地址，通常第一个是最原始的客户端 IP
        client_ip = x_forwarded_for.split(',')[0].strip()

    # 获取 IP 地址所在地
    location = "苏州"
    # try:
    #     ip_location = get_ip_location(client_ip)
    #     location = ip_location["city"]
    # except Exception as e:
    #     # 处理获取 IP 地址所在地时发生的错误
    #     location = '中国'
    #     print("IP定位异常：", e)

    device = Device.query.filter_by(mac=params.mac).first()
    if not device:
        new_device = Device(
            mac=params.mac,
            location=location
        )
        db.session.add(new_device)
        db.session.commit()
    else:
        device.location = location
        db.session.commit()

    device = Device.query.filter_by(mac=params.mac).first()
    ud = UserDevice.query.filter_by(device_id=device.id).first()
    # roleAgentInfo = RoleAgent.query.filter_by(id=device.role_agent_id).first()
    # voice = Voice.query.filter_by(id=device.voice_id).first()
    rs["data"] = {
        "id": device.id,
        "mac": device.mac,
        "user_id":ud.user_id if ud else device.mac,
        "name": device.name,
        "introduction": device.introduction,
        "create_time": device.create_time.strftime("%Y-%m-%d %H:%M:%S"),
        "update_time": device.create_time.strftime("%Y-%m-%d %H:%M:%S"),
        "location": device.location,
        "memory": device.memory,
        "role_agent_id": device.role_agent_id,
        "role_agent":{
            "character_id":device.role_agent.character_id,
            "name":device.role_agent.name,
            "introduction":device.role_agent.introduction,
            "basic_information":device.role_agent.basic_information,
            "traits":device.role_agent.traits,
        },
        "character_id": device.role_agent.character_id,
        "history": device.history,
        "voice_name": device.voice.name,
    }
    return jsonify(rs)

@app.route("/deviceBank/createDevice", methods=['POST'])
def createDevice():
    rs = {"code": 200, "msg": "创建成功", "data": {}}
    params = DeviceInput(**request.json)
    location = '北京'
    new_device = Device(
        name=params.name,
        introduction=params.introduction,
        mac=params.mac,
        location=location
    )
    db.session.add(new_device)
    db.session.commit()
    rs["data"] = {}
    return jsonify(rs)

@app.route("/user/updateFriends", methods=['POST'])
@jwt_required()
def updateFriends():
    rs = {"code": 200, "msg": "改动亲友联动人成功", "data": {}}
    # params = DeviceInput(**request.json)
    friends = request.json.get("friends")
    
    # 获取当前用户ID
    current_user_id = get_jwt_identity()
    
    # 查询用户
    user = User.query.filter_by(id=current_user_id).first()
    # role_agent = RoleAgent.query.get(params.id)
    if user:
        user.friends = json.dumps(friends,ensure_ascii=False)
        db.session.commit()
    rs["data"] = {}
    return jsonify(rs)

class RegisterUserDeviceInput(BaseModel):
    mac: str
@app.route("/deviceBank/registerUserDevice", methods=['POST'])
@jwt_required()
def registerUserDevice():
    rs = {"code": 200, "msg": "注册成功", "data": {}}
    try:
        params = RegisterUserDeviceInput(**request.json)
        
        # 查询设备
        device = Device.query.filter_by(mac=params.mac).first()
        if not device:
            # 创建新设备
            new_device = Device(
                mac=params.mac,
                name=f"没有名字的小星-{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",  # 默认名称可以根据需要调整
                introduction="主人懒得给小星写简介",
                role_agent_id=3
            )
            db.session.add(new_device)
            db.session.commit()
            device = Device.query.filter_by(mac=params.mac).first()
            # device = new_device
        
        # 获取当前用户ID
        current_user_id = get_jwt_identity()
        # 查询用户
        user = User.query.filter_by(id=current_user_id).first()
        if not user:
            rs["code"] = 404
            rs["msg"] = "用户未找到"
            return jsonify(rs)
         # 先删除所有符合 user_id 和 device_id 的旧记录
        delete_count = UserDevice.query.filter_by(
            device_id=device.id  # 确保 device 对象已正确获取
        ).delete()
        # 统一提交事务（同时处理删除和新增）
        db.session.commit()
    
        # 检查是否已存在相同的用户和设备记录
        existing_record = UserDevice.query.filter_by(user_id=user.id, device_id=device.id).first()
        if existing_record:
            rs["code"] = 409
            rs["msg"] = "用户和设备已关联"
            return jsonify(rs)
        
        # 创建新的用户设备记录
        new_user_device = UserDevice(
            user_id=user.id,
            device_id=device.id
        )
        db.session.add(new_user_device)
        db.session.commit()
        
    except Exception as e:
        db.session.rollback()
        rs["code"] = 500
        rs["msg"] = f"服务器错误: {str(e)}"
    
    return jsonify(rs)

@app.route("/deviceBank/postChatRecord", methods=['POST'])
def postChatRecord():
    rs = {"code": 200, "msg": "记录成功", "data": {}}
    params = ChatRecordInput(**request.json)
    device = Device.query.filter_by(mac=params.device_mac).first()
    print("设备提交记录信息：", params)
    new_record = ChatRecord(
        device_id=device.id,
        device_mac=params.device_mac,
        user_question=params.user_question,
        assistant_reply=params.assistant_reply
    )
    db.session.add(new_record)
    db.session.commit()
    print("新增一个记录::")

    records = ChatRecord.query.filter_by(device_mac=params.device_mac).order_by(ChatRecord.create_time.desc()).all()
    # 风险识别
    content = '''
    <你的角色>\n
    心理风险识别专家，必须且仅返回json结构体\n
    <你的任务>\n
    - 只根据近期对话记录，请你帮我观察我中是否有心理风险 隐患。\n
    - 如果没有，则返回{{"code":200,"keywords":""}}\n
    - 如果有，则返回{{"code":500,"keywords":"风险内容，如因xxx原因产生自杀、不想活了的想法或表达"}}\n
    - keywords不要超过20个字，简洁明了传达关键信息\n
    <注意事项>\n
    - 请你只关注近期聊天记录是否包含风险。\n
    - 若近期对话记录包含风险，则从历史聊天记录中分析风险原因和事件。\n
    - 注意要减少误报率，因为你的风险识别结果我会发送给家长，请确保严谨，没有问题不要强行提示风险。\n
    - 若近期对话记录没有风险，则不要再考虑历史对话记录，并返回{{"code":200,"keywords":""}}\n
    <近期对话记录，仅用于进行风险识别>\n
    {}\n
   
    '''.format('\n'.join(['{}:{}'.format("我", r.user_question) for r in records[:1]])[:500])
    # '''.format('\n'.join(['{}:{}'.format("我", r.user_question) for r in records[:1]])[:500],'\n'.join(['{}:{}'.format("我", r.user_question) for r in records[2:]])[:100000])
     # <历史对话记录，仅用于再有风险的情况下，分析风险原因，不用于风险识别，就算历史对话记录有风险，也不要提示有风险>\n
    # {}\n
    print(content)
    header = {
        "Authorization": "Bearer fastgpt-pyhYXrdHtJgesG1UARULUradUsU8QTPVdGsHUYReMyxegKtbNPiFIJAXWRQvC",
        "Content-Type": "application/json",
    }
    reqParams = {"stream": False, "messages": [{"content": content, "role": "user"}]}
    resp = requests.post(
        "https://model.zscampus.com/api/v1/chat/completions", headers=header, json=reqParams
    )
    try:
        risk = resp.json()['choices'][0]['message']['content']
        match = re.search(r'\{.*?\}', risk, re.DOTALL)
        print("风险识别结果1：",risk,"正则结果：",match)
        if match:
            risk = match.group(0)  # 获取匹配到的内容（包含 {}）
            print("风险识别结果2：：",risk)  # 输出：{intent: '播放音乐 [中秋月]'}
            # intent = result
            risk = json.loads(risk)
            if risk["code"]==500:
                print("进入短信报警流程")
                ud = UserDevice.query.filter_by(device_id=device.id).first()
                user = User.query.filter_by(id=ud.user_id).first()
                if user.friends:
                    print("用户亲友关系：：",user.friends)
                    for f in json.loads(user.friends):
                        sendMessage(f["tele"],{"user_nick":f"{user.nickname}","date":f"{datetime.now()}","keywords":f"{risk['keywords']}"},"SMS_480260073")
                else:
                    print("用户没有亲友，取消亲友报警")
                # [{"nickname": "", "tele": "", "relation": "朋友", "isVerified": false}]
    except Exception as e:
        print("风险识别出问题::", e)
        pass
    
    
    # 记录更新
    content = '''
    <你的角色>
    你是聊天记录分析专家
    <你的任务>
    根据对话记录，请你帮我总结一个故事背景或线索，方便你和我共同保存聊天记忆。
    你只发给我故事记录即可，其他多余的话不要说。
    <对话记录>
    {}
    '''.format('\n'.join(['{}:{}\n{}:{}'.format("我", r.user_question, "你", r.assistant_reply) for r in records])[:100000])
    header = {
        "Authorization": "Bearer fastgpt-pyhYXrdHtJgesG1UARULUradUsU8QTPVdGsHUYReMyxegKtbNPiFIJAXWRQvC",
        "Content-Type": "application/json",
    }
    reqParams = {"stream": False, "messages": [{"content": content, "role": "user"}]}
    resp = requests.post(
        "https://model.zscampus.com/api/v1/chat/completions", headers=header, json=reqParams
    )
    memory = ""
    try:
        memory = resp.json()['choices'][0]['message']['content']
    except Exception as e:
        print("记忆抽取出问题", e)
        pass

    device = Device.query.filter_by(mac=params.device_mac).first()
    if device:
        device.memory = memory
        db.session.commit()
        
    rs["data"] = memory
    return jsonify(rs)

@app.route("/deviceBank/updateDevice", methods=['POST'])
def updateDevice():
    rs = {"code": 200, "msg": "修改成功", "data": {}}
    params = DeviceInput(**request.json)
    device = Device.query.filter_by(id=params.id).first()
    if device:
        device.name = params.name
        device.introduction = params.introduction
        device.role_agent_id = params.role_agent_id
        device.location = params.location
        device.voice_id = params.voice_id
        db.session.commit()
    else:
        rs["code"] = 404
        rs["msg"] = "设备未找到"
    return jsonify(rs)

import re
import json
from ast import literal_eval

def genPsyReport(records):
    # print(records[0])
    content = '''
    <对话记录>
    {}

    '''.format('\n'.join(['{}:{}\n{}:{}'.format("我", r.user_question, "AI", r.assistant_reply) for r in records])[:3000])
    
    print("开始生产报告了::",content)
    header = {
        "Authorization": "Bearer fastgpt-eMVKLPyPNSvjIf4zDBOMfOy7fYm4RymqaJIVLczKPnbdMtnP63fE",
        "Content-Type": "application/json",
    }
    reqParams = {"stream": False, "messages": [{"content": content, "role": "user"}]}
    resp = requests.post(
        "https://model.zscampus.com/api/v1/chat/completions", headers=header, json=reqParams
    )
    think=""
    content=""
    report = ""
    try:
        report = resp.json()['choices'][0]['message']['content']
        think, content = parseReprot(report)
    except Exception as e:
        print("记忆抽取出问题", e,resp.json())
        pass
    return think, content


@app.route("/user/getReportList", methods=['GET'])
@jwt_required()
def getReportList():
    current_user_id = get_jwt_identity()
    rs = {"code": 200, "msg": "获取成功", "data": {}}
    reports = UserReport.query.filter_by(user_id=current_user_id).order_by(UserReport.create_time.desc()).all()
    
    # print(devices)
    data = [
        {
            "id": report.id,
            "create_time": report.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            "content": json.loads(report.content),
            "think": report.think,
            "type":report.type
        }
        for report in reports
    ]
    rs["data"] = data
    return jsonify(rs)

@app.route("/user/getReportById", methods=['POST'])
def getReportById():
    id = request.json.get('id')
    rs = {"code": 200, "msg": "获取成功", "data": {}}
    report = UserReport.query.filter_by(id=id).first()
    
    # print(devices)
    data = {
            "id": report.id,
            "create_time": report.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            "content": json.loads(report.content),
            "think": report.think,
            "type":report.type
        }
    
    rs["data"] = data
    return jsonify(rs)


@app.route("/user/deleteReportById", methods=['POST'])
@jwt_required()
def deleteReportById():
    current_user_id = get_jwt_identity()
    rs = {"code": 200, "msg": "删除成功", "data": {}}
    
    id = request.json.get('id')
    report = UserReport.query.filter_by(id=id).first()
    if report:
        db.session.delete(report)
        db.session.commit()
        
    return jsonify(rs)

@app.route("/user/genReport", methods=['POST'])
@jwt_required()
def genReport():
    current_user_id = get_jwt_identity()
    rs = {"code": 200, "msg": "分析成功", "data": {}}
    # params = DeviceInput(**request.json)
    userDevices = UserDevice.query.filter_by(user_id=current_user_id).all()
    print("需要生成测评的用户ID：",current_user_id,"用户的设备：",userDevices)
    records = []
    for ud in userDevices:
        device_records = ChatRecord.query.filter_by(device_id=ud.device_id).order_by(ChatRecord.create_time.desc()).all()
        records.extend(device_records
                       )
    # device = Device.query.filter_by(id=params.id).first()
    if records and len(records)>0:
        # print(records)
        # print(records[0])
        # device.name = params.name
        # device.introduction = params.introduction
        # device.role_agent_id = params.role_agent_id
        # device.location = params.location
        # device.voice_id = params.voice_id
        # db.session.commit()
        think, content = genPsyReport(records)
        # print("生成报告了::",think, content)
        new_report = UserReport(
            user_id=current_user_id,
            content=json.dumps(content,ensure_ascii=False),
            think=think,
            type="chat"
        )
        db.session.add(new_report)
        db.session.commit()
    # else:
    #     rs["code"] = 404
    #     rs["msg"] = "设备未找到"
    return jsonify(rs)

@app.route("/deviceBank/getDeviceList", methods=['POST'])
def getDeviceList():
    rs = {"code": 200, "msg": "查询成功", "data": {}}
    devices = Device.query.order_by(Device.create_time.desc()).all()
    # print(devices)
    data = [
        {
            "id": device.id,
            "mac": device.mac,
            "name": device.name,
            "introduction": device.introduction,
            "create_time": device.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            "update_time": device.update_time.strftime("%Y-%m-%d %H:%M:%S"),
            "location": device.location,
            "memory": device.memory,
            "role_agent_id": device.role_agent_id,
            "history": device.history
        }
        for device in devices
    ]
    rs["data"] = data
    return jsonify(rs)

@app.route("/deviceBank/deleteDevice", methods=['POST'])
def deleteDevice():
    rs = {"code": 200, "msg": "删除成功", "data": {}}
    params = DeviceInput(**request.json)
    device = Device.query.filter_by(id=params.id).first()
    if device:
        db.session.delete(device)
        db.session.commit()
    else:
        rs["code"] = 404
        rs["msg"] = "设备未找到"
    return jsonify(rs)

@app.route("/deviceBank/getDeviceInfo", methods=['POST'])
def getDeviceInfo():
    rs = {"code": 200, "msg": "查询成功", "data": {}}
    params = DeviceInput(**request.json)
    device = Device.query.filter_by(id=params.id).first()
    if device:
        history = []
        records = ChatRecord.query.filter_by(device_mac=device.mac).order_by(ChatRecord.create_time.asc()).all()
        for record in records:
            history.append({"role": "user", "content": record.user_question, "create_time": record.create_time.strftime("%Y-%m-%d %H:%M:%S")})
            history.append({"role": "assistant", "content": record.assistant_reply, "create_time": record.create_time.strftime("%Y-%m-%d %H:%M:%S")})
        # 获取关联的role_agent和voice信息
        role_agent_data = {
            "id": device.role_agent.id,
            "character_id": device.role_agent.character_id,
            "name": device.role_agent.name,
            "introduction": device.role_agent.introduction,
            "basic_information": device.role_agent.basic_information,
            "opening_line": device.role_agent.opening_line,
            "traits": device.role_agent.traits,
            "chat_example": device.role_agent.chat_example,
            "type": device.role_agent.type,
            "chat_objective": device.role_agent.chat_objective,
            "sound": device.role_agent.sound,
            "create_time": device.role_agent.create_time.strftime("%Y-%m-%d %H:%M:%S"),
        } if device.role_agent else {}

        voice_data = {
            "id": device.voice.id,
            "name": device.voice.name,
            "alias": device.voice.alias,
            "create_time": device.voice.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            "update_time": device.voice.update_time.strftime("%Y-%m-%d %H:%M:%S") if device.voice.update_time else None,
        } if device.voice else {}

        data = {
            "id": device.id,
            "mac": device.mac,
            "name": device.name,
            "introduction": device.introduction,
            "create_time": device.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            "update_time": device.update_time.strftime("%Y-%m-%d %H:%M:%S"),
            "location": device.location,
            "memory": device.memory,
            "role_agent_id": device.role_agent_id,
            "voice_id": device.voice_id,
            "history": history,
            "role_agent": role_agent_data,
            "voice": voice_data
        }
        rs["data"] = data
    else:
        rs["code"] = 404
        rs["msg"] = "查询失败"
    return jsonify(rs)

###音色库



from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest
def build_request(api_name, method):
    request = CommonRequest()
    domain = 'nls-slp.cn-shanghai.aliyuncs.com'
    version = '2019-08-19'
    request.set_domain(domain)
    request.set_version(version)
    request.set_action_name(api_name)
    request.set_method(method)
    request.set_protocol_type('https')
    return request


class Voice(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    user_id = db.Column(db.Integer, nullable=False)
    create_time = db.Column(db.DateTime, default=china_time)
    update_time = db.Column(db.DateTime, onupdate=china_time, default=china_time)
    alias = db.Column(db.String(255), nullable=True)
    devices = db.relationship('Device', back_populates='voice')
from pydub import AudioSegment
import tempfile
@app.route("/voiceBank/createVoice", methods=['POST'])
@jwt_required()
def createVoice():
    current_user_id = get_jwt_identity()
    rs = {"code": 200, "msg": "创建成功", "data": {}}
    form_data = request.form
    file = request.files['file']
    
    if not file or not form_data.get('alias') or not current_user_id:
        rs["code"] = 400
        rs["msg"] = "缺少必要的参数"
        return jsonify(rs)
    
    filename = f"{uuid.uuid4().hex}.mp3"
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    file.save(file_path)
    
    
    # 转换为MP3
    audio = AudioSegment.from_file(file_path)
    # filename = f"{uuid.uuid4().hex}.mp3"
    # file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    audio.export(file_path, format="mp3", bitrate="128k")
    
    # file_url = "http://oss.healhub.cn/录音.m4a"
    file_url = f"https://{request.host}/api/uploads/{filename}"
    print(file_url)
    try:
        voice_name = cosy_clone(file_url)
        print(voice_name)
        new_voice = Voice(
            name=voice_name,
            user_id=current_user_id,
            alias=form_data.get('alias')
        )
        db.session.add(new_voice)
        db.session.commit()
        rs["data"] = {
            "id": new_voice.id,
            "name": new_voice.name,
            "user_id": new_voice.user_id,
            "create_time": new_voice.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            "update_time": new_voice.update_time.strftime("%Y-%m-%d %H:%M:%S"),
            "alias": new_voice.alias
        }
    except Exception as e:
        rs["code"] = 500
        rs["msg"] = "克隆失败,{}".format(e)
        rs["data"] = {}
    
    return jsonify(rs)

@app.route("/voiceBank/updateVoice", methods=['POST'])
def updateVoice():
    rs = {"code": 200, "msg": "修改成功", "data": {}}
    params = request.json
    voice = Voice.query.filter_by(id=params.get('id')).first()
    if voice:
        voice.name = params.get('name', voice.name)
        voice.alias = params.get('alias', voice.alias)
        db.session.commit()
    else:
        rs["code"] = 404
        rs["msg"] = "音色未找到"
    return jsonify(rs)

@app.route("/voiceBank/deleteVoice", methods=['POST'])
def deleteVoice():
    rs = {"code": 200, "msg": "删除成功", "data": {}}
    params = request.json
    voice = Voice.query.filter_by(id=params.get('id')).first()
    if voice:
        db.session.delete(voice)
        db.session.commit()
    else:
        rs["code"] = 404
        rs["msg"] = "音色未找到"
    return jsonify(rs)

@app.route("/voiceBank/getVoiceList", methods=['POST'])
def getVoiceList():
    rs = {"code": 200, "msg": "查询成功", "data": {}}
    voices = Voice.query.order_by(Voice.create_time.desc()).all()
    data = [
        {
            "id": voice.id,
            "name": voice.name,
            "user_id": voice.user_id,
            "create_time": voice.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            # "update_time": voice.update_time.strftime("%Y-%m-%d %H:%M:%S"),
            "alias": voice.alias
        }
        for voice in voices
    ]
    rs["data"] = data
    return jsonify(rs)

@app.route("/voiceBank/getVoiceInfo", methods=['POST'])
def getVoiceInfo():
    rs = {"code": 200, "msg": "查询成功", "data": {}}
    params = request.json
    voice = Voice.query.filter_by(id=params.get('id')).first()
    if voice:
        data = {
            "id": voice.id,
            "name": voice.name,
            "user_id": voice.user_id,
            "create_time": voice.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            "update_time": voice.update_time.strftime("%Y-%m-%d %H:%M:%S"),
            "alias": voice.alias
        }
        rs["data"] = data
    else:
        rs["code"] = 404
        rs["msg"] = "查询失败"
    return jsonify(rs)

@app.route("/voiceBank/uploadAudio", methods=['POST'])
def uploadAudio():
    file = request.files['file']
    if not file:
        return jsonify({"code": 400, "msg": "No file uploaded"}), 400
    
    filename = f"{uuid.uuid4().hex}.mp3"
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    file.save(file_path)
    
    return jsonify({"code": 200, "msg": "音频上传成功", "data": {"filename": filename}}), 200


def create_token():
    
    # 创建request，并设置参数。
    request = CommonRequest()
    request.set_method('POST')
    request.set_domain('nls-meta.cn-shanghai.aliyuncs.com')
    request.set_version('2019-02-28')
    request.set_action_name('CreateToken')
    # try:
    client = AcsClient(required_vars['ALIBABA_CLOUD_ACCESS_KEY_ID'], required_vars['ALIBABA_CLOUD_ACCESS_KEY_SECRET'], "cn-shanghai")
    response = client.do_action_with_exception(request)
    # print(response)
    jss = json.loads(response)
    if 'Token' in jss and 'Id' in jss['Token']:
        token = jss['Token']['Id']
        expireTime = jss['Token']['ExpireTime']
        print("token = " + token)
        print("expireTime = " + str(expireTime))
        return token
    else:
        raise Exception("Failed to get token")
    # except Exception as e:
    #     print(e)
    #     return None

def tts_synthesize(text, voice_name, token, appkey):
    url = "wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1"
    headers = {
        "Content-Type": "application/json"
    }
    payload = {
        "header": {
            "namespace": "SpeechSynthesizer",
            "name": "Recognize",
            "message_id": str(uuid.uuid4()),
            "app_key": appkey,
            "token": token
        },
        "payload": {
            "text": text,
            "voice": voice_name,
            "aformat": "wav",
            "sample_rate": 24000,
            "volume": 50,
            "speech_rate": 0,
            "pitch_rate": 0
        }
    }
    response = requests.post(url, headers=headers, json=payload)
    if response.status_code == 200:
        audio_url = response.json().get('audio_url')
        return audio_url
    else:
        return None
    
    
import nls
@app.route("/voiceBank/tts", methods=['POST'])
def tts():
    rs = {"code": 200, "msg": "合成成功", "data": {}}
    params = request.json
    text = params.get('text')
    voice_name = params.get('voice_name')
    
    if not text or not voice_name:
        rs["code"] = 400
        rs["msg"] = "缺少必要的参数"
        return jsonify(rs)
    model = "cosyvoice-v1"

    synthesizer = SpeechSynthesizer(model=model, voice=voice_name)
    audio = synthesizer.call(text)
    print('requestId: ', synthesizer.get_last_request_id())
    audio_filename = f"{uuid.uuid4().hex}.wav"
    audio_filepath = os.path.join(app.config['UPLOAD_FOLDER'], audio_filename)
    with open(audio_filepath, 'wb') as f:
        f.write(audio)
    audio_url = f"https://{request.host}/api/uploads/{audio_filename}"
    print(audio_url)
    rs["data"] = {"audio_url": audio_url}
    
    return jsonify(rs)



import nls
@app.route("/voiceBank/ttsBinary", methods=['POST'])
def ttsBinary():
    rs = {"code": 200, "msg": "合成成功", "data": {}}
    params = request.json
    text = params.get('text')
    voice_name = params.get('voice_name')
    
    if not text or not voice_name:
        rs["code"] = 400
        rs["msg"] = "缺少必要的参数"
        return jsonify(rs)
    model = "cosyvoice-v1"

    synthesizer = SpeechSynthesizer(model=model, voice=voice_name,format=AudioFormat.WAV_16000HZ_MONO_16BIT,volume=100,speech_rate=1.2)
    audio = synthesizer.call(text)
    # print('requestId: ', synthesizer.get_last_request_id())
    # audio_filename = f"{uuid.uuid4().hex}.wav"
    # audio_filepath = os.path.join(app.config['UPLOAD_FOLDER'], audio_filename)
    # with open(audio_filepath, 'wb') as f:
    #     f.write(audio)
    # audio_url = f"https://{request.host}/api/uploads/{audio_filename}"
    # print(audio_url)
    # rs["data"] = {"audio_url": audio_url}

    # 直接返回音频二进制流
    return Response(
        audio,
        mimetype='audio/wav',
        headers={
            'Content-Disposition': 'inline; filename="generated_audio.wav"',
            'Cache-Control': 'no-store'
        }
    )
    
    # return jsonify(rs)


@app.route('/uploads/<path:filename>', methods=['GET'])
def download_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)


def fetchJSONFromLLM(s):
    # 优化后的正则表达式方案
    try:
        # 使用非贪婪匹配完整JSON结构（包含可能的嵌套）
        pattern = r'```json\s*({.*?})\s*```'  # 处理代码块包裹的情况
        match = re.search(pattern, s, re.DOTALL)
        
        # 如果无代码块包裹，使用扩展匹配
        if not match:
            # 匹配从第一个{开始到最后一个}结束的完整结构
            pattern = r'({(?:[^{}]*|{[^{}]*})*})'  # 处理简单嵌套
            match = re.search(pattern, s, re.DOTALL)

        if match:
            raw_json = match.group(1)
            
            # 清理可能的格式残留
            clean_json = re.sub(r'(?<!\\)\\.', '', raw_json)  # 处理转义字符
            clean_json = clean_json.replace('\n', '')  # 保留必要换行符的替代方案
            
            # 使用严格JSON解析验证
            # report_data = json.loads(clean_json, strict=False)
            
            return clean_json
            
    except json.JSONDecodeError as e:
        print(f"JSON解析失败: {str(e)}")
        # 添加错误日志记录
    except Exception as e:
        print(f"系统错误: {str(e)}")


def genHoroscope(user:User):
    
    if user.birthday:
        prompt = '''
        请根据以下用户信息和占卜规则生成每日运势报告，并严格以JSON格式返回结果：

        用户信息输入要求：
        {}

        生成规则：
        1. 总体运势值（0-100）：基于星象位置和用户近期状态综合计算
        2. 分项运势包含（每项0-100）：
        - 恋爱运love
        - 学业/事业运career
        - 金钱运finance
        - 健康运health
        - 人际运social
        - 出行运travel
        3. 运势描述应包含：
        - 当日能量解读（30字内）
        - 幸运建议（具体可操作建议）
        - 注意事项（潜在风险预警）
        4. 生成三个幸运物推荐（包含emoji）

        示例参考结构：
        {{
        "overall_score": 88,
        "detailed_scores": {{
            "love": 92,
            "career": 85,
            "finance": 78,
            "health": 80,
            "social": 90,
            "travel":80
        }},
        "forecast": {{
            "energy_analysis": "金星入庙带来和谐能量",
            "lucky_tips": ["佩戴银色饰品", "14:00重要会议"],
            "cautions": ["避免冲动投资"]
        }},
        "lucky_objects": ["💼", "🌿", "✉️"]
        }}

        输出要求：
        - 严格遵循示例JSON格式
        - 数值需符合逻辑关系（如单项均分接近总分）
        - 运势描述使用口语化中文
        - 幸运物避免重复
        '''.format("昵称：{}，生日：{}".format(user.nickname,user.birthday))
        
        header = {
            "Authorization": "Bearer fastgpt-mwfpqQgwIogvfd5x9ACrcaRGvGD1CoSkpaLi7x1oileAHgav8ol8oGkus1rMK",
            "Content-Type": "application/json",
        }
        reqParams = {"stream": False, "messages": [{"content": prompt, "role": "user"}]}
        resp = requests.post(
            "https://model.zscampus.com/api/v1/chat/completions", headers=header, json=reqParams
        )
        try:
            report = resp.json()['choices'][0]['message']['content']
            # print("原始报告：",report)
            report = fetchJSONFromLLM(report)
            # match = re.search(r'\{.*?\}', prompt, re.DOTALL)
            # print("正则结果：",match)
            if report:
                # report = match.group(0)  # 获取匹配到的内容（包含 {}）
                print("运势测评报告：",report)  # 输出：{intent: '播放音乐 [中秋月]'}
                # report = report.replace('\n','')
                # intent = result
                # report = json.loads(report)
                
                new_report = UserReport(
                    user_id=user.id,
                    content=report,
                    think="",
                    type="horoscope"
                )
                db.session.add(new_report)
                db.session.commit()
                return report
        except Exception as e:
            db.session.rollback()
            print(e)
            return None
        

import time
from sqlalchemy import and_
@app.route('/wechat/login', methods=['POST'])
def wechat_login():
    rs = {"code": 200, "msg": "成功", "data": {}}
    #考研
    #app_id = 'wxf24ac2bde6d36547'  # 替换为你的小程序 app id
    #app_secret = '3feb58fa4d969bff9c6d948c03b55610'  # 替换为你的小程序 app secret
    time.sleep(1)
    #禾帆
    app_id = 'wx439cbfa227b71a5d'  # 替换为你的小程序 app id
    app_secret = '3978ecdb5bc4828c407417ef82608426'  # 替换为你的小程序 app secret
    code = request.json.get('code')

    if not code:
        return jsonify({'error': 'Code is required'}), 400

    url = f'https://api.weixin.qq.com/sns/jscode2session?appid={app_id}&secret={app_secret}&js_code={code}&grant_type=authorization_code'
    response = requests.get(url)
    result = response.json()

    if 'openid' in result:
        openid = result['openid']
        user = User.query.filter_by(openid=openid).first()
        
        if not user:
            # 如果用户不存在，则插入新用户
            new_user = User(openid=openid)
            db.session.add(new_user)
            db.session.commit()
            user = new_user
            # 获取当天时间范围
            
        today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
        today_end = datetime.now()
        access_token = create_access_token(identity=str(user.id), expires_delta=timedelta(hours=102400))
        horoscope = None
        # 查询今日报告
        horoscope_report = None
        try:
            horoscope_report = UserReport.query.filter(
                and_(
                    UserReport.user_id == user.id,
                    UserReport.create_time >= today_start,
                    UserReport.create_time <= today_end,
                    UserReport.type == 'horoscope'
                )
            ).order_by(UserReport.create_time.desc()).first()
            print("运势报告结果：：",horoscope_report)
            if horoscope_report:
                horoscope_report = horoscope_report
                horoscope_report = json.loads(horoscope_report.content)
                print(horoscope_report)
            else:
                horoscope_report = json.loads(genHoroscope(user=user))
        except Exception as e:
            # session.rollback()
            print(e)
            pass
        friends=[]
        try:
            friends = json.loads(user.friends)
        except:
            friends = []
        rs["data"] = {'username': user.username,
                    'nickname': user.nickname,
                    'introduction': user.introduction,
                    'email': user.email,
                    'friends':friends,
                    'tele':user.tele,
                    'birthday':user.birthday,
                    'horoscope_report':horoscope_report,
                    'openid':user.openid,'access_token':access_token,"id":user.id}
        return jsonify(rs)
    else:
        rs["code"] = 500
        rs["msg"] = "Failed to get openid"
        rs["data"] = result
        return jsonify(rs), 400

@app.route('/user/update', methods=['POST'])
@jwt_required()
def update_user():
    current_user_id = get_jwt_identity()
    data = request.json
    nickname = data.get('nickname')
    introduction = data.get('introduction')
    tele = data.get('tele')
    birthday = data.get('birthday')

    if not nickname or not introduction:
        return jsonify({"code": 400, "msg": "Missing required fields"}), 400

    user = User.query.get(current_user_id)
    if not user:
        return jsonify({"code": 404, "msg": "User not found"}), 404

    user.nickname = nickname
    user.introduction = introduction
    user.birthday = birthday
    user.tele = tele
    db.session.commit()

    return jsonify({"code": 200, "msg": "Success", "data": {'username': user.username,
                    'nickname': user.nickname,
                    'introduction': user.introduction,
                    'tele': user.tele,
                    'birthday': user.birthday,
                    'email': user.email,
                    'openid':user.openid}}), 200




#测评
class Assessment(db.Model):
    __tablename__ = 'assessments'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    description = db.Column(db.String(500))
    cover_img = db.Column(db.String(200))
    question_count = db.Column(db.Integer, nullable=False)
    type = db.Column(db.String(20))  # MBTI/personality/mental_health等
    scoring_logic = db.Column(db.String(20), default='sum')  # 计分策略: sum/mbti/phq9等
    created_at = db.Column(db.DateTime, default=china_time)
    updated_at = db.Column(db.DateTime, onupdate=china_time)
    questions = db.relationship('Question', backref='assessment', lazy=True)
    results = db.relationship('Result', backref='assessment', lazy=True)

class Question(db.Model):
    __tablename__ = 'questions'
    id = db.Column(db.Integer, primary_key=True)
    assessment_id = db.Column(db.Integer, db.ForeignKey('assessments.id'), nullable=False)
    content = db.Column(db.String(500), nullable=False)
    dimension = db.Column(db.String(50))  # 维度标识（如MBTI的E/I）
    reverse_scoring = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=china_time)
    options = db.relationship('Option', backref='question', lazy=True, cascade="all, delete-orphan")

class Option(db.Model):
    __tablename__ = 'options'
    id = db.Column(db.Integer, primary_key=True)
    question_id = db.Column(db.Integer, db.ForeignKey('questions.id'), nullable=False)
    content = db.Column(db.String(200), nullable=False)
    score = db.Column(db.Integer, nullable=False)  # 根据测评类型不同含义不同
    created_at = db.Column(db.DateTime, default=china_time)

class Result(db.Model):
    __tablename__ = 'results'
    id = db.Column(db.Integer, primary_key=True)
    assessment_id = db.Column(db.Integer, db.ForeignKey('assessments.id'), nullable=False)
    min_score = db.Column(db.Integer)  # 总分制测评使用
    max_score = db.Column(db.Integer)
    title = db.Column(db.String(100), nullable=False)
    description = db.Column(db.Text)
    professional_advice = db.Column(db.Text)  # 专业建议
    image = db.Column(db.String(200))
    created_at = db.Column(db.DateTime, default=china_time)

@app.route('/eva/assessments')
def get_assessments():
    assessments = Assessment.query.all()
    return jsonify([{
        'id': a.id,
        'title': a.title,
        'description': a.description,
        'cover_img': a.cover_img,
        'question_count': a.question_count,
        'type': a.type,
        'scoring_logic': a.scoring_logic  # 新增计分逻辑字段
    } for a in assessments])

@app.route('/eva/assessment/<int:aid>')
def get_assessment(aid):
    assessment = Assessment.query.get(aid)
    questions = Question.query.filter_by(assessment_id=aid).all()
    
    data = {
        'id': assessment.id,
        'title': assessment.title,
        'type': assessment.type,
        'scoring_logic': assessment.scoring_logic,
        'questions': [{
            'id': q.id,
            'content': q.content,
            'dimension': q.dimension,
            'reverse_scoring': q.reverse_scoring,
            'options': [{
                'id': o.id,
                'content': o.content,
                'score': o.score
            } for o in q.options]
        } for q in questions]
    }
    return jsonify(data)

@app.route('/eva/calculate', methods=['POST'])
@jwt_required()
def calculate():
    rs = {"code":200,"data":{},"msg":"计算成功"}
    data = request.json
    current_user_id = get_jwt_identity()
    assessment_id = data['assessment_id']
    answers = data['answers']  # {question_id: option_id, ...}
    type = 'eva'
    assessment = Assessment.query.get(assessment_id)
    scoring_logic = assessment.scoring_logic
    
    report = None
    # else:
    if scoring_logic == 'mbti':
        report = calculate_mbti(assessment_id, answers,assessment)
    elif scoring_logic == 'sum':
        report = calculate_sum(assessment_id, answers,assessment)
    elif scoring_logic == 'phq9':
        report = calculate_phq9(assessment_id, answers,assessment)
    
    if report:
        new_report = UserReport(
            user_id=current_user_id,
            content=json.dumps(report,ensure_ascii=False),
            think="",
            type=type
        )
        
        db.session.add(new_report)
        db.session.commit()
        rs['data'] = report
        return  jsonify(rs), 200
    
    rs['data'] = 'Unknown scoring logic'
    return jsonify(rs), 400


from collections import defaultdict
def calculate_mbti(aid, answers,assessment):
    try:
        dimension_scores = defaultdict(lambda: defaultdict(int))
        questions = Question.query.filter_by(assessment_id=aid).all()
        
        for q in questions:
            if str(q.id) not in answers:
                continue
            option_score = answers[str(q.id)]
            dimension = q.dimension
            if dimension in ['EI', 'SN', 'TF', 'JP']:
                dim_key = dimension[0] if option_score > 0 else dimension[1]
                dimension_scores[dimension][dim_key] += abs(option_score)
                
        type_code = ''.join([
            'E' if dimension_scores['EI']['E'] > dimension_scores['EI']['I'] else 'I',
            'S' if dimension_scores['SN']['S'] > dimension_scores['SN']['N'] else 'N',
            'T' if dimension_scores['TF']['T'] > dimension_scores['TF']['F'] else 'F',
            'J' if dimension_scores['JP']['J'] > dimension_scores['JP']['P'] else 'P'
        ])
        
        result = Result.query.filter_by(assessment_id=aid, title=type_code).first()
        return {
            'assessment_name':assessment.title,
            'title': result.title,
            'type_code': type_code,
            'dimension_scores': dimension_scores,
            'description': result.description,
            'professional_advice': result.professional_advice,
            'image': result.image,
            'scoring_logic': 'mbti'
        }
    except Exception as e:
        return {'error': str(e)}

def calculate_sum(aid, answers,assessment):
    total = 0
    for q_id, o_id in answers.items():
        option = Option.query.get(o_id)
        question = Question.query.get(q_id)
        if question.reverse_scoring:
            total += (option.score * -1)  # 简单反向计分处理
        else:
            total += option.score
    
    result = Result.query.filter(
        Result.assessment_id == aid,
        Result.min_score <= total,
        Result.max_score >= total
    ).first()
    
    return {
        'assessment_name':assessment.title,
        'score': total,
        'title': result.title,
        'description': result.description,
        'professional_advice': result.professional_advice,
        'image': result.image
    }

def calculate_phq9(aid, answers,assessment):
    try:
        score_map = {0: 0, 1: 1, 2: 2, 3: 3}
        total = 0
        
        # 验证所有问题都回答
        questions = Question.query.filter_by(assessment_id=aid).all()
        if len(answers) != len(questions):
            return jsonify({'error': '请完成所有题目'}), 400

        # 计算总分
        for q_id, o_id in answers.items():
            option = Option.query.get(o_id)
            total += score_map.get(option.score, 0)
        
        # 查询结果（关键修改：使用分数范围而非title匹配）
        result = Result.query.filter(
            Result.assessment_id == aid,
            Result.min_score <= total,
            Result.max_score >= total
        ).first()

        if not result:
            return {'error': '未找到对应结果'}

        # 特殊处理自杀风险项（第8题）
        suicide_risk = False
        q8 = Question.query.filter_by(assessment_id=aid, content='过去两周，您出现自杀念头或自伤行为的频率？').first()
        if q8 and answers.get(str(q8.id), 0) >= 2:  # 得分≥2表示存在风险
            suicide_risk = True

        return {
            'assessment_name':assessment.title,
            'score': total,
            'severity': result.title,
            'title': result.title,
            'description': result.description,
            'professional_advice': result.professional_advice,
            'image': result.image,
            'scoring_logic':"phq9",
            'suicide_risk': suicide_risk  # 新增危机预警标识
        }
    except Exception as e:
        return {'error': str(e)}
    
    
##音频库

class Audio(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    img = db.Column(db.String(300), nullable=False)
    artist = db.Column(db.String(50))
    album = db.Column(db.String(50))
    duration = db.Column(db.Integer)  # 时长（秒）
    url = db.Column(db.String(200), unique=True, nullable=False)
    upload_time = db.Column(db.DateTime, default=china_time)

    def to_dict(self):
        return {
            "id": self.id,
            "title": self.title,
            "artist": self.artist,
            "album": self.album,
            "img": self.img,
            "duration": self.duration,
            "url": self.url,
            "upload_time": self.upload_time.strftime("%Y-%m-%d %H:%M:%S")
        }

@app.route('/music/audios', methods=['POST'])
def create_audio():
    # 处理文件上传
    if 'file' not in request.files:
        return jsonify({"error": "No file part"}), 400
    
    file = request.files['file']
    if file.filename == '':
        return jsonify({"error": "No selected file"}), 400
    
    if file and allowed_file(file.filename):
        # 保存文件
        filename = secure_filename(file.filename)
        save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(save_path)
        
        # 创建音频记录
        audio = Audio(
            title=request.form.get('title', 'Untitled'),
            artist=request.form.get('artist', 'Unknown'),
            album=request.form.get('album', ''),
            duration=int(request.form.get('duration', 0)),
            file_path=save_path
        )
        
        db.session.add(audio)
        try:
            db.session.commit()
            return jsonify(audio.to_dict()), 201
        except Exception as e:
            db.session.rollback()
            return jsonify({"error": str(e)}), 500
    else:
        return jsonify({"error": "Invalid file type"}), 400

@app.route('/music/audios', methods=['GET'])
def get_all_audios():
    rs = {"code":200,"data":{},"msg":""}
    audios = Audio.query.all()
    rs["data"]=[audio.to_dict() for audio in audios]
    return jsonify(rs)

@app.route('/music/audios/<int:audio_id>', methods=['GET'])
def get_audio(audio_id):
    rs = {"code":200,"data":{},"msg":""}
    audio = Audio.query.get(audio_id)
    if audio:
        rs["data"]=audio.to_dict()
        return jsonify(rs)
    else:
        rs["code"]=500
        rs["msg"] = '音频不存在'
        return jsonify(rs), 404

@app.route('/music/audios/<int:audio_id>', methods=['PUT'])
def update_audio(audio_id):
    audio = Audio.query.get(audio_id)
    if not audio:
        return jsonify({"error": "Audio not found"}), 404
    
    data = request.json
    audio.title = data.get('title', audio.title)
    audio.artist = data.get('artist', audio.artist)
    audio.album = data.get('album', audio.album)
    audio.duration = data.get('duration', audio.duration)
    
    try:
        db.session.commit()
        return jsonify(audio.to_dict())
    except Exception as e:
        db.session.rollback()
        return jsonify({"error": str(e)}), 500

@app.route('/music/audios/<int:audio_id>', methods=['DELETE'])
def delete_audio(audio_id):
    audio = Audio.query.get(audio_id)
    if not audio:
        return jsonify({"error": "Audio not found"}), 404
    
    try:
        # 删除文件
        if os.path.exists(audio.file_path):
            os.remove(audio.file_path)
        
        db.session.delete(audio)
        db.session.commit()
        return jsonify({"message": "Audio deleted successfully"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"error": str(e)}), 500
    
# monkey.patch_all()
if __name__ == '__main__':
    cache_fragments = {}
    with app.app_context():
        db.create_all()
        
        
    # http_server = WSGIServer(('0.0.0.0', 8012), app)
    # http_server.serve_forever()
    app.run(host="0.0.0.0", port=8012, debug=False)